diff options
Diffstat (limited to 'platform/macos/src')
26 files changed, 0 insertions, 5844 deletions
diff --git a/platform/macos/src/MGLAnnotationImage.h b/platform/macos/src/MGLAnnotationImage.h deleted file mode 100644 index d7336133d1..0000000000 --- a/platform/macos/src/MGLAnnotationImage.h +++ /dev/null @@ -1,65 +0,0 @@ -#import <AppKit/AppKit.h> - -#import "MGLFoundation.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - The `MGLAnnotationImage` class is responsible for presenting point-based - annotations visually on an `MGLMapView` instance. Annotation image objects pair - `NSImage` objects with annotation-related metadata. They may be recycled later - and put into a reuse queue that is maintained by the map view. - */ -MGL_EXPORT -@interface MGLAnnotationImage : NSObject <NSSecureCoding> - -#pragma mark Initializing and Preparing the Image Object - -/** - Initializes and returns a new annotation image object. - - @param image The image to display for the annotation. - @param reuseIdentifier The string that identifies this annotation image in the - reuse queue. - @return The initialized annotation image object or `nil` if there was a problem - initializing the object. - */ -+ (instancetype)annotationImageWithImage:(NSImage *)image reuseIdentifier:(NSString *)reuseIdentifier; - -#pragma mark Getting and Setting Attributes - -/** The image to display for the annotation. */ -@property (nonatomic, readonly) NSImage *image; - -/** - The string that identifies this annotation image in the reuse queue. - (read-only) - - You specify the reuse identifier when you create the image object. You use this - type later to retrieve an annotation image object that was created previously - but which is currently unused because its annotation is not on-screen. - - If you define distinctly different types of annotations (with distinctly - different annotation images to go with them), you can differentiate between the - annotation types by specifying different reuse identifiers for each one. - */ -@property (nonatomic, readonly) NSString *reuseIdentifier; - -/** - A Boolean value indicating whether the annotation is selectable. - - The default value of this property is `YES`. If the value of this property is - `NO`, the annotation image ignores click events and cannot be selected. - */ -@property (nonatomic, getter=isSelectable) BOOL selectable; - -/** - The cursor that appears above any annotation using this annotation image. - - By default, this property is set to `nil`, representing the current cursor. - */ -@property (nonatomic, nullable) NSCursor *cursor; - -@end - -NS_ASSUME_NONNULL_END diff --git a/platform/macos/src/MGLAnnotationImage.m b/platform/macos/src/MGLAnnotationImage.m deleted file mode 100644 index 8d715b427b..0000000000 --- a/platform/macos/src/MGLAnnotationImage.m +++ /dev/null @@ -1,63 +0,0 @@ -#import "MGLAnnotationImage_Private.h" - -@interface MGLAnnotationImage () - -@property (nonatomic) NSImage *image; -@property (nonatomic) NSString *reuseIdentifier; -@property (nonatomic, strong, nullable) NSString *styleIconIdentifier; - -@end - -@implementation MGLAnnotationImage - -+ (instancetype)annotationImageWithImage:(NSImage *)image reuseIdentifier:(NSString *)reuseIdentifier { - return [[self alloc] initWithImage:image reuseIdentifier:reuseIdentifier]; -} - -- (instancetype)initWithImage:(NSImage *)image reuseIdentifier:(NSString *)reuseIdentifier { - if (self = [super init]) { - _image = image; - _reuseIdentifier = [reuseIdentifier copy]; - _selectable = YES; - } - return self; -} - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (instancetype)initWithCoder:(NSCoder *)decoder { - if (self = [super init]) { - _image = [decoder decodeObjectOfClass:[NSImage class] forKey:@"image"]; - _reuseIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"reuseIdentifier"]; - _cursor = [decoder decodeObjectOfClass:[NSCursor class] forKey:@"cursor"]; - _selectable = [decoder decodeBoolForKey:@"selectable"]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [coder encodeObject:_image forKey:@"image"]; - [coder encodeObject:_reuseIdentifier forKey:@"reuseIdentifier"]; - [coder encodeObject:_cursor forKey:@"cursor"]; - [coder encodeBool:_selectable forKey:@"selectable"]; -} - -- (BOOL)isEqual:(id)other { - if (self == other) return YES; - if (![other isKindOfClass:[MGLAnnotationImage class]]) return NO; - - MGLAnnotationImage *otherAnnotationImage = other; - - return ((!_reuseIdentifier && !otherAnnotationImage.reuseIdentifier) || [_reuseIdentifier isEqualToString:otherAnnotationImage.reuseIdentifier]) - && _selectable == otherAnnotationImage.selectable - && ((!_cursor && !otherAnnotationImage.cursor) || [_cursor isEqual:otherAnnotationImage.cursor]) - && (_image == otherAnnotationImage.image || [[_image TIFFRepresentation] isEqualToData:[otherAnnotationImage.image TIFFRepresentation]]); -} - -- (NSUInteger)hash { - return _reuseIdentifier.hash + @(_selectable).hash + _image.hash; -} - -@end diff --git a/platform/macos/src/MGLAnnotationImage_Private.h b/platform/macos/src/MGLAnnotationImage_Private.h deleted file mode 100644 index 428f1db5d9..0000000000 --- a/platform/macos/src/MGLAnnotationImage_Private.h +++ /dev/null @@ -1,8 +0,0 @@ -#import "Mapbox.h" - -@interface MGLAnnotationImage (Private) - -/// Unique identifier of the sprite image used by the style to represent the receiver’s `image`. -@property (nonatomic, strong, nullable) NSString *styleIconIdentifier; - -@end diff --git a/platform/macos/src/MGLAttributionButton.h b/platform/macos/src/MGLAttributionButton.h deleted file mode 100644 index 3e0b53a6d0..0000000000 --- a/platform/macos/src/MGLAttributionButton.h +++ /dev/null @@ -1,25 +0,0 @@ -#import <Cocoa/Cocoa.h> - -#import "MGLFoundation.h" -#import "MGLTypes.h" - -NS_ASSUME_NONNULL_BEGIN - -@class MGLAttributionInfo; - -/// Button that looks like a hyperlink and opens a URL. -MGL_EXPORT -@interface MGLAttributionButton : NSButton - -/// Returns an `MGLAttributionButton` instance with the given info. -- (instancetype)initWithAttributionInfo:(MGLAttributionInfo *)info; - -/// The URL to open and display as a tooltip. -@property (nonatomic, readonly, nullable) NSURL *URL; - -/// Opens the URL. -- (IBAction)openURL:(nullable id)sender; - -@end - -NS_ASSUME_NONNULL_END diff --git a/platform/macos/src/MGLAttributionButton.mm b/platform/macos/src/MGLAttributionButton.mm deleted file mode 100644 index 3df415f60d..0000000000 --- a/platform/macos/src/MGLAttributionButton.mm +++ /dev/null @@ -1,55 +0,0 @@ -#import "MGLAttributionButton.h" -#import "MGLAttributionInfo.h" - -#import "NSBundle+MGLAdditions.h" -#import "NSString+MGLAdditions.h" - -@implementation MGLAttributionButton - -- (instancetype)initWithAttributionInfo:(MGLAttributionInfo *)info { - if (self = [super initWithFrame:NSZeroRect]) { - self.bordered = NO; - self.bezelStyle = NSRegularSquareBezelStyle; - - // Extract any prefix consisting of intellectual property symbols. - NSScanner *scanner = [NSScanner scannerWithString:info.title.string]; - NSCharacterSet *symbolSet = [NSCharacterSet characterSetWithCharactersInString:@"©℗®℠™ &"]; - NSString *symbol; - [scanner scanCharactersFromSet:symbolSet intoString:&symbol]; - - // Remove the underline from the symbol for aesthetic reasons. - NSMutableAttributedString *title = info.title.mutableCopy; - [title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, symbol.length)]; - - self.attributedTitle = title; - [self sizeToFit]; - - _URL = info.URL; - if (_URL) { - self.toolTip = _URL.absoluteString; - } - - self.target = self; - self.action = @selector(openURL:); - } - return self; -} - -- (BOOL)wantsLayer { - return YES; -} - -- (void)resetCursorRects { - if (self.URL) { - // The whole button gets a pointing hand cursor, just like a hyperlink. - [self addCursorRect:self.bounds cursor:[NSCursor pointingHandCursor]]; - } -} - -- (IBAction)openURL:(__unused id)sender { - if (self.URL) { - [[NSWorkspace sharedWorkspace] openURL:self.URL]; - } -} - -@end diff --git a/platform/macos/src/MGLCompassCell.h b/platform/macos/src/MGLCompassCell.h deleted file mode 100644 index 5ed70dcb06..0000000000 --- a/platform/macos/src/MGLCompassCell.h +++ /dev/null @@ -1,5 +0,0 @@ -#import <Cocoa/Cocoa.h> - -/// Circular slider with an arrow pointing north. -@interface MGLCompassCell : NSSliderCell -@end diff --git a/platform/macos/src/MGLCompassCell.m b/platform/macos/src/MGLCompassCell.m deleted file mode 100644 index b3a4ad4544..0000000000 --- a/platform/macos/src/MGLCompassCell.m +++ /dev/null @@ -1,34 +0,0 @@ -#import "MGLCompassCell.h" - -@implementation MGLCompassCell - -- (instancetype)init { - if (self = [super init]) { - self.sliderType = NSCircularSlider; - // A tick mark for each cardinal direction. - self.numberOfTickMarks = 4; - // This slider goes backwards! - self.minValue = -360; - self.maxValue = 0; - } - return self; -} - -- (void)drawKnob:(NSRect)knobRect { - // Draw a red triangle pointing whichever way the slider is facing. - NSBezierPath *trianglePath = [NSBezierPath bezierPath]; - [trianglePath moveToPoint:NSMakePoint(NSMinX(knobRect), NSMaxY(knobRect))]; - [trianglePath lineToPoint:NSMakePoint(NSMaxX(knobRect), NSMaxY(knobRect))]; - [trianglePath lineToPoint:NSMakePoint(NSMidX(knobRect), NSMinY(knobRect))]; - [trianglePath closePath]; - NSAffineTransform *transform = [NSAffineTransform transform]; - [transform translateXBy:NSMidX(knobRect) yBy:NSMidY(knobRect)]; - [transform scaleBy:0.8]; - [transform rotateByDegrees:self.doubleValue]; - [transform translateXBy:-NSMidX(knobRect) yBy:-NSMidY(knobRect)]; - [trianglePath transformUsingAffineTransform:transform]; - [[NSColor redColor] setFill]; - [trianglePath fill]; -} - -@end diff --git a/platform/macos/src/MGLMapView+IBAdditions.h b/platform/macos/src/MGLMapView+IBAdditions.h deleted file mode 100644 index 29d914a7d9..0000000000 --- a/platform/macos/src/MGLMapView+IBAdditions.h +++ /dev/null @@ -1,68 +0,0 @@ -#import <Foundation/Foundation.h> - -#import "MGLMapView.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface MGLMapView (IBAdditions) - -#if TARGET_INTERFACE_BUILDER - -// Core properties that can be manipulated in the Attributes inspector in -// Interface Builder. These redeclarations merely add the IBInspectable keyword. -// They appear here to ensure that they appear above the convenience properties; -// inspectables declared in MGLMapView.h are always sorted before those in -// MGLMapView+IBAdditions.h, due to ASCII sort order. - -// We want this property to look like a URL bar in the Attributes inspector, but -// just calling it styleURL would violate Cocoa naming conventions and conflict -// with the existing NSURL property. Fortunately, IB strips out the two -// underscores for display. - -/** URL of the style currently displayed in the receiver. - - The URL may be a full HTTP or HTTPS URL, a Mapbox - style URL (`mapbox://styles/<user>/<style>`), or a path to a local file - relative to the application’s resource path. Leave this field blank for the - default style. */ -@property (nonatomic, nullable) IBInspectable NSString *styleURL__; - -// Convenience properties related to the initial viewport. These properties -// are not meant to be used outside of Interface Builder. latitude and longitude -// are backed by properties of type CLLocationDegrees, but these declarations -// must use the type double because Interface Builder is unaware that -// CLLocationDegrees is a typedef for double. - -/** The initial center latitude. */ -@property (nonatomic) IBInspectable double latitude; - -/** The initial center longitude. */ -@property (nonatomic) IBInspectable double longitude; - -@property (nonatomic) IBInspectable double zoomLevel; - -// Renamed properties. Interface Builder derives the display name of each -// inspectable from the runtime name, but runtime names don’t always make sense -// in UI. - -/** A Boolean value that determines whether the user may zoom the map, changing - its zoom level. */ -@property (nonatomic) IBInspectable BOOL allowsZooming; - -/** A Boolean value that determines whether the user may scroll around the map, - changing its center coordinate. */ -@property (nonatomic) IBInspectable BOOL allowsScrolling; - -/** A Boolean value that determines whether the user may rotate the map, - changing its direction. */ -@property (nonatomic) IBInspectable BOOL allowsRotating; - -/** A Boolean value that determines whether the user may tilt the map, changing - its pitch. */ -@property (nonatomic) IBInspectable BOOL allowsTilting; - -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/platform/macos/src/MGLMapView+IBAdditions.mm b/platform/macos/src/MGLMapView+IBAdditions.mm deleted file mode 100644 index 0c65abc031..0000000000 --- a/platform/macos/src/MGLMapView+IBAdditions.mm +++ /dev/null @@ -1,120 +0,0 @@ -#import "MGLMapView+IBAdditions.h" - -#import "MGLStyle.h" - -#import "MGLMapView_Private.h" - -@implementation MGLMapView (IBAdditions) - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingStyleURL__ { - return [NSSet setWithObject:@"styleURL"]; -} - -- (nullable NSString *)styleURL__ { - return self.styleURL.absoluteString; -} - -- (void)setStyleURL__:(nullable NSString *)URLString { - URLString = [URLString stringByTrimmingCharactersInSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet]]; - NSURL *url = URLString.length ? [NSURL URLWithString:URLString] : nil; - if (URLString.length && !url) { - [NSException raise:MGLInvalidStyleURLException - format:@"“%@” is not a valid style URL.", URLString]; - } - self.styleURL = url; -} - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingLatitude { - return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; -} - -- (double)latitude { - return self.centerCoordinate.latitude; -} - -- (void)setLatitude:(double)latitude { - if (!isnan(self.pendingLongitude)) { - // With both components present, set the real center coordinate and - // forget the pending parts. - self.centerCoordinate = CLLocationCoordinate2DMake(latitude, self.pendingLongitude); - self.pendingLatitude = NAN; - self.pendingLongitude = NAN; - } else { - // Not enough info to make a valid center coordinate yet. Stash this - // latitude away until the longitude is set too. - self.pendingLatitude = latitude; - } -} - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingLongitude { - return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; -} - -- (double)longitude { - return self.centerCoordinate.longitude; -} - -- (void)setLongitude:(double)longitude { - if (!isnan(self.pendingLatitude)) { - // With both components present, set the real center coordinate and - // forget the pending parts. - self.centerCoordinate = CLLocationCoordinate2DMake(self.pendingLatitude, longitude); - self.pendingLatitude = NAN; - self.pendingLongitude = NAN; - } else { - // Not enough info to make a valid center coordinate yet. Stash this - // longitude away until the latitude is set too. - self.pendingLongitude = longitude; - } -} - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingAllowsZooming { - return [NSSet setWithObject:@"zoomEnabled"]; -} - -- (BOOL)allowsZooming { - return self.zoomEnabled; -} - -- (void)setAllowsZooming:(BOOL)allowsZooming { - self.zoomEnabled = allowsZooming; -} - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingAllowsScrolling { - return [NSSet setWithObject:@"scrollEnabled"]; -} - -- (BOOL)allowsScrolling { - return self.scrollEnabled; -} - -- (void)setAllowsScrolling:(BOOL)allowsScrolling { - self.scrollEnabled = allowsScrolling; -} - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingAllowsRotating { - return [NSSet setWithObject:@"rotateEnabled"]; -} - -- (BOOL)allowsRotating { - return self.rotateEnabled; -} - -- (void)setAllowsRotating:(BOOL)allowsRotating { - self.rotateEnabled = allowsRotating; -} - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingAllowsTilting { - return [NSSet setWithObject:@"pitchEnabled"]; -} - -- (BOOL)allowsTilting { - return self.pitchEnabled; -} - -- (void)setAllowsTilting:(BOOL)allowsTilting { - self.pitchEnabled = allowsTilting; -} - -@end diff --git a/platform/macos/src/MGLMapView+Impl.h b/platform/macos/src/MGLMapView+Impl.h deleted file mode 100644 index d33a19dbab..0000000000 --- a/platform/macos/src/MGLMapView+Impl.h +++ /dev/null @@ -1,44 +0,0 @@ -#import <mbgl/gfx/renderer_backend.hpp> -#import <mbgl/map/map_observer.hpp> -#import <mbgl/util/image.hpp> - -@class MGLMapView; - -typedef struct _CGLContextObject* CGLContextObj; - -class MGLMapViewImpl : public mbgl::MapObserver { -public: - static std::unique_ptr<MGLMapViewImpl> Create(MGLMapView*); - - MGLMapViewImpl(MGLMapView*); - virtual ~MGLMapViewImpl() = default; - - virtual mbgl::gfx::RendererBackend& getRendererBackend() = 0; - - // We need a static image of what was rendered for printing. - virtual mbgl::PremultipliedImage readStillImage() = 0; - - virtual CGLContextObj getCGLContextObj() { - return nullptr; - } - - // mbgl::MapObserver implementation - void onCameraWillChange(mbgl::MapObserver::CameraChangeMode) override; - void onCameraIsChanging() override; - void onCameraDidChange(mbgl::MapObserver::CameraChangeMode) override; - void onWillStartLoadingMap() override; - void onDidFinishLoadingMap() override; - void onDidFailLoadingMap(mbgl::MapLoadError mapError, const std::string& what) override; - void onWillStartRenderingFrame() override; - void onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus) override; - void onWillStartRenderingMap() override; - void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode) override; - void onDidFinishLoadingStyle() override; - void onSourceChanged(mbgl::style::Source& source) override; - void onDidBecomeIdle() override; - bool onCanRemoveUnusedStyleImage(const std::string& imageIdentifier) override; - -protected: - /// Cocoa map view that this adapter bridges to. - __weak MGLMapView *mapView = nullptr; -}; diff --git a/platform/macos/src/MGLMapView+Impl.mm b/platform/macos/src/MGLMapView+Impl.mm deleted file mode 100644 index c00f858153..0000000000 --- a/platform/macos/src/MGLMapView+Impl.mm +++ /dev/null @@ -1,101 +0,0 @@ -#import "MGLMapView+Impl.h" -#import "MGLMapView+OpenGL.h" -#import "MGLStyle_Private.h" -#import "NSBundle+MGLAdditions.h" - -#include <mbgl/map/map.hpp> -#include <mbgl/style/style.hpp> - -std::unique_ptr<MGLMapViewImpl> MGLMapViewImpl::Create(MGLMapView* nativeView) { - return std::make_unique<MGLMapViewOpenGLImpl>(nativeView); -} - -MGLMapViewImpl::MGLMapViewImpl(MGLMapView* nativeView_) : mapView(nativeView_) { -} - -void MGLMapViewImpl::onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode) { - bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; - [mapView cameraWillChangeAnimated:animated]; -} - -void MGLMapViewImpl::onCameraIsChanging() { - [mapView cameraIsChanging]; -} - -void MGLMapViewImpl::onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode) { - bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; - [mapView cameraDidChangeAnimated:animated]; -} - -void MGLMapViewImpl::onWillStartLoadingMap() { - [mapView mapViewWillStartLoadingMap]; -} - -void MGLMapViewImpl::onDidFinishLoadingMap() { - [mapView mapViewDidFinishLoadingMap]; -} - -void MGLMapViewImpl::onDidFailLoadingMap(mbgl::MapLoadError mapError, const std::string& what) { - NSString *description; - MGLErrorCode code; - switch (mapError) { - case mbgl::MapLoadError::StyleParseError: - code = MGLErrorCodeParseStyleFailed; - description = NSLocalizedStringWithDefaultValue(@"PARSE_STYLE_FAILED_DESC", nil, nil, @"The map failed to load because the style is corrupted.", @"User-friendly error description"); - break; - case mbgl::MapLoadError::StyleLoadError: - code = MGLErrorCodeLoadStyleFailed; - description = NSLocalizedStringWithDefaultValue(@"LOAD_STYLE_FAILED_DESC", nil, nil, @"The map failed to load because the style can't be loaded.", @"User-friendly error description"); - break; - case mbgl::MapLoadError::NotFoundError: - code = MGLErrorCodeNotFound; - description = NSLocalizedStringWithDefaultValue(@"STYLE_NOT_FOUND_DESC", nil, nil, @"The map failed to load because the style can’t be found or is incompatible.", @"User-friendly error description"); - break; - default: - code = MGLErrorCodeUnknown; - description = NSLocalizedStringWithDefaultValue(@"LOAD_MAP_FAILED_DESC", nil, nil, @"The map failed to load because an unknown error occurred.", @"User-friendly error description"); - } - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: description, - NSLocalizedFailureReasonErrorKey: @(what.c_str()), - }; - NSError *error = [NSError errorWithDomain:MGLErrorDomain code:code userInfo:userInfo]; - [mapView mapViewDidFailLoadingMapWithError:error]; -} - -void MGLMapViewImpl::onWillStartRenderingFrame() { - [mapView mapViewWillStartRenderingFrame]; -} - -void MGLMapViewImpl::onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) { - bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; - [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered]; -} - -void MGLMapViewImpl::onWillStartRenderingMap() { - [mapView mapViewWillStartRenderingMap]; -} - -void MGLMapViewImpl::onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) { - bool fullyRendered = mode == mbgl::MapObserver::RenderMode::Full; - [mapView mapViewDidFinishRenderingMapFullyRendered:fullyRendered]; -} - -void MGLMapViewImpl::onDidBecomeIdle() { - [mapView mapViewDidBecomeIdle]; -} - -void MGLMapViewImpl::onDidFinishLoadingStyle() { - [mapView mapViewDidFinishLoadingStyle]; -} - -void MGLMapViewImpl::onSourceChanged(mbgl::style::Source& source) { - NSString *identifier = @(source.getID().c_str()); - MGLSource * nativeSource = [mapView.style sourceWithIdentifier:identifier]; - [mapView sourceDidChange:nativeSource]; -} - -bool MGLMapViewImpl::onCanRemoveUnusedStyleImage(const std::string &imageIdentifier) { - NSString *imageName = [NSString stringWithUTF8String:imageIdentifier.c_str()]; - return [mapView shouldRemoveStyleImage:imageName]; -} diff --git a/platform/macos/src/MGLMapView+OpenGL.h b/platform/macos/src/MGLMapView+OpenGL.h deleted file mode 100644 index d4c6a448cd..0000000000 --- a/platform/macos/src/MGLMapView+OpenGL.h +++ /dev/null @@ -1,45 +0,0 @@ -#import "MGLMapView+Impl.h" -#import "MGLMapView_Private.h" - -#include <mbgl/gfx/renderable.hpp> -#include <mbgl/gl/renderer_backend.hpp> - -/// Adapter responsible for bridging calls from mbgl to MGLMapView and Cocoa. -class MGLMapViewOpenGLImpl final : public MGLMapViewImpl, - public mbgl::gl::RendererBackend, - public mbgl::gfx::Renderable { -public: - MGLMapViewOpenGLImpl(MGLMapView*); - ~MGLMapViewOpenGLImpl() override = default; - -public: - void restoreFramebufferBinding(); - - // Implementation of mbgl::gfx::RendererBackend -public: - mbgl::gfx::Renderable& getDefaultRenderable() override { - return *this; - } - -private: - void activate() override; - void deactivate() override; - // End implementation of mbgl::gfx::RendererBackend - - // Implementation of mbgl::gl::RendererBackend -public: - void updateAssumedState() override; - -private: - mbgl::gl::ProcAddress getExtensionFunctionPointer(const char* name) override; - // End implementation of mbgl::gl::Rendererbackend - - // Implementation of MGLMapViewImpl -public: - mbgl::gfx::RendererBackend& getRendererBackend() override { - return *this; - } - - mbgl::PremultipliedImage readStillImage() override; - CGLContextObj getCGLContextObj() override; -}; diff --git a/platform/macos/src/MGLMapView+OpenGL.mm b/platform/macos/src/MGLMapView+OpenGL.mm deleted file mode 100644 index f6168a4b80..0000000000 --- a/platform/macos/src/MGLMapView+OpenGL.mm +++ /dev/null @@ -1,89 +0,0 @@ -#import "MGLMapView+OpenGL.h" -#import "MGLOpenGLLayer.h" - -#include <mbgl/gl/renderable_resource.hpp> - -#import <OpenGL/gl.h> - -class MGLMapViewOpenGLRenderableResource final : public mbgl::gl::RenderableResource { -public: - MGLMapViewOpenGLRenderableResource(MGLMapViewOpenGLImpl& backend_) : backend(backend_) { - } - - void bind() override { - backend.restoreFramebufferBinding(); - } - -private: - MGLMapViewOpenGLImpl& backend; - -public: - // The current framebuffer of the NSOpenGLLayer we are painting to. - GLint fbo = 0; - - // The reference counted count of activation calls - NSUInteger activationCount = 0; -}; - -MGLMapViewOpenGLImpl::MGLMapViewOpenGLImpl(MGLMapView* nativeView_) - : MGLMapViewImpl(nativeView_), - mbgl::gl::RendererBackend(mbgl::gfx::ContextMode::Unique), - mbgl::gfx::Renderable(mapView.framebufferSize, - std::make_unique<MGLMapViewOpenGLRenderableResource>(*this)) { - - // Install the OpenGL layer. Interface Builder’s synchronous drawing means - // we can’t display a map, so don’t even bother to have a map layer. - mapView.layer = - mapView.isTargetingInterfaceBuilder ? [CALayer layer] : [MGLOpenGLLayer layer]; -} - -mbgl::gl::ProcAddress MGLMapViewOpenGLImpl::getExtensionFunctionPointer(const char* name) { - static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - if (!framework) { - throw std::runtime_error("Failed to load OpenGL framework."); - } - - return reinterpret_cast<mbgl::gl::ProcAddress>(CFBundleGetFunctionPointerForName( - framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name])); -} - -void MGLMapViewOpenGLImpl::activate() { - auto& resource = getResource<MGLMapViewOpenGLRenderableResource>(); - if (resource.activationCount++) { - return; - } - - MGLOpenGLLayer* layer = (MGLOpenGLLayer*)mapView.layer; - [layer.openGLContext makeCurrentContext]; -} - -void MGLMapViewOpenGLImpl::deactivate() { - auto& resource = getResource<MGLMapViewOpenGLRenderableResource>(); - if (--resource.activationCount) { - return; - } - - [NSOpenGLContext clearCurrentContext]; -} - -void MGLMapViewOpenGLImpl::updateAssumedState() { - auto& resource = getResource<MGLMapViewOpenGLRenderableResource>(); - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &resource.fbo); - assumeFramebufferBinding(resource.fbo); - assumeViewport(0, 0, mapView.framebufferSize); -} - -void MGLMapViewOpenGLImpl::restoreFramebufferBinding() { - auto& resource = getResource<MGLMapViewOpenGLRenderableResource>(); - setFramebufferBinding(resource.fbo); - setViewport(0, 0, mapView.framebufferSize); -} - -mbgl::PremultipliedImage MGLMapViewOpenGLImpl::readStillImage() { - return readFramebuffer(mapView.framebufferSize); -} - -CGLContextObj MGLMapViewOpenGLImpl::getCGLContextObj() { - MGLOpenGLLayer* layer = (MGLOpenGLLayer*)mapView.layer; - return layer.openGLContext.CGLContextObj; -} diff --git a/platform/macos/src/MGLMapView.h b/platform/macos/src/MGLMapView.h deleted file mode 100644 index 374d4eeab7..0000000000 --- a/platform/macos/src/MGLMapView.h +++ /dev/null @@ -1,1247 +0,0 @@ -#import <Cocoa/Cocoa.h> -#import <CoreLocation/CoreLocation.h> - -#import "MGLFoundation.h" -#import "MGLTypes.h" -#import "MGLGeometry.h" - -NS_ASSUME_NONNULL_BEGIN - -@class MGLAnnotationImage; -@class MGLMapCamera; -@class MGLStyle; -@class MGLShape; - -@protocol MGLAnnotation; -@protocol MGLMapViewDelegate; -@protocol MGLOverlay; -@protocol MGLFeature; - -/** - An interactive, customizable map view with an interface similar to the one - provided by Apple’s MapKit. - - Using `MGLMapView`, you can embed the map inside a view, allow users to - manipulate it with standard gestures, animate the map between different - viewpoints, and present information in the form of annotations and overlays. - - The map view loads scalable vector tiles that conform to the - <a href="https://github.com/mapbox/vector-tile-spec">Mapbox Vector Tile Specification</a>. - It styles them with a style that conforms to the - <a href="https://www.mapbox.com/mapbox-gl-style-spec/">Mapbox Style Specification</a>. - Such styles can be designed in - <a href="https://www.mapbox.com/studio/">Mapbox Studio</a> and hosted on - mapbox.com. - - A collection of Mapbox-hosted styles is available through the `MGLStyle` class. - These basic styles use - <a href="https://www.mapbox.com/developers/vector-tiles/mapbox-streets">Mapbox Streets</a> - or <a href="https://www.mapbox.com/satellite/">Mapbox Satellite</a> data - sources, but you can specify a custom style that makes use of your own data. - - Mapbox-hosted vector tiles and styles require an API access token, which you - can obtain from the - <a href="https://www.mapbox.com/studio/account/tokens/">Mapbox account page</a>. - Access tokens associate requests to Mapbox’s vector tile and style APIs with - your Mapbox account. They also deter other developers from using your styles - without your permission. - - Adding your own gesture recognizer to `MGLMapView` will block the corresponding - gesture recognizer built into `MGLMapView`. To avoid conflicts, define which - gesture recognizer takes precedence. For example, you can subclass - `NSClickGestureRecognizer` and override `-[NSGestureRecognizer shouldRequireFailureOfGestureRecognizer:]`, - so that your subclass will be invoked only if the default `MGLMapView` click - gesture recognizer fails: - - ```swift - class MapClickGestureRecognizer: NSClickGestureRecognizer { - override func shouldRequireFailure(of otherGestureRecognizer: NSGestureRecognizer) -> Bool { - return otherGestureRecognizer is NSClickGestureRecognizer - } - } - ``` - - @note You are responsible for getting permission to use the map data and for - ensuring that your use adheres to the relevant terms of use. - */ -MGL_EXPORT IB_DESIGNABLE -@interface MGLMapView : NSView - -#pragma mark Creating Instances - -/** - Initializes and returns a newly allocated map view with the specified frame and - the default style. - - @param frame The frame for the view, measured in points. - @return An initialized map view. - */ -- (instancetype)initWithFrame:(NSRect)frame; - -/** - Initializes and returns a newly allocated map view with the specified frame and - style URL. - - @param frame The frame for the view, measured in points. - @param styleURL URL of the map style to display. The URL may be a full HTTP or - HTTPS URL, a Mapbox style URL - (`mapbox://styles/<user>/<style>`), or a path to a local file relative to - the application’s resource path. Specify `nil` for the default style. - @return An initialized map view. - */ -- (instancetype)initWithFrame:(NSRect)frame styleURL:(nullable NSURL *)styleURL; - -#pragma mark Accessing the Delegate - -/** - The receiver’s delegate. - - A map view sends messages to its delegate to notify it of changes to its - contents or the viewpoint. The delegate also provides information about - annotations displayed on the map, such as the styles to apply to individual - annotations. - */ -@property (nonatomic, weak, nullable) IBOutlet id <MGLMapViewDelegate> delegate; - -#pragma mark Configuring the Map’s Appearance - -/** - The style currently displayed in the receiver. - - Unlike the `styleURL` property, this property is set to an object that allows - you to manipulate every aspect of the style locally. - - If the style is loading, this property is set to `nil` until the style finishes - loading. If the style has failed to load, this property is set to `nil`. - Because the style loads asynchronously, you should manipulate it in the - `-[MGLMapViewDelegate mapView:didFinishLoadingStyle:]` or - `-[MGLMapViewDelegate mapViewDidFinishLoadingMap:]` method. It is not possible - to manipulate the style before it has finished loading. - - @note The default styles provided by Mapbox contain sources and layers with - identifiers that will change over time. Applications that use APIs that - manipulate a style's sources and layers must first set the style URL to an - explicitly versioned style using a convenience method like - `+[MGLStyle outdoorsStyleURLWithVersion:]`, `MGLMapView`’s “Style URL” - inspectable in Interface Builder, or a manually constructed `NSURL`. - */ -@property (nonatomic, readonly, nullable) MGLStyle *style; - -/** - URL of the style currently displayed in the receiver. - - The URL may be a full HTTP or HTTPS URL, a Mapbox - style URL (`mapbox://styles/<user>/<style>`), or a path to a local file relative - to the application’s resource path. - - If you set this property to `nil`, the receiver will use the default style and - this property will automatically be set to that style’s URL. - - If you want to modify the current style without replacing it outright, or if - you want to introspect individual style attributes, use the `style` property. - */ -@property (nonatomic, null_resettable) NSURL *styleURL; - -/** - Reloads the style. - - You do not normally need to call this method. The map view automatically - responds to changes in network connectivity by reloading the style. You may - need to call this method if you change the access token after a style has - loaded but before loading a style associated with a different Mapbox account. - */ -- (IBAction)reloadStyle:(id)sender; - -/** - A control for zooming in and out, positioned in the lower-right corner. - */ -@property (nonatomic, readonly) NSSegmentedControl *zoomControls; - -/** - A control indicating the map’s direction and allowing the user to manipulate - the direction, positioned above the zoom controls in the lower-right corner. - */ -@property (nonatomic, readonly) NSSlider *compass; - -/** - The Mapbox logo, positioned in the lower-left corner. - - @note The Mapbox terms of service, which governs the use of Mapbox-hosted - vector tiles and styles, - <a href="https://www.mapbox.com/help/mapbox-logo/">requires</a> most Mapbox - customers to display the Mapbox logo. If this applies to you, do not hide - this view or change its contents. - */ -@property (nonatomic, readonly) NSImageView *logoView; - -/** - A view showing legally required copyright notices, positioned along the bottom - of the map view, to the left of the Mapbox logo. - - @note The Mapbox terms of service, which governs the use of Mapbox-hosted - vector tiles and styles, - <a href="https://www.mapbox.com/help/attribution/">requires</a> these - copyright notices to accompany any map that features Mapbox-designed styles, - OpenStreetMap data, or other Mapbox data such as satellite or terrain data. - If that applies to this map view, do not hide this view or remove any - notices from it. - */ -@property (nonatomic, readonly) NSView *attributionView; - -/** - A Boolean value indicating whether the map should prefetch tiles. - - When this property is set to `YES`, the map view prefetches tiles designed for - a low zoom level and displays them until receiving more detailed tiles for the - current zoom level. The prefetched tiles typically contain simplified versions - of each shape, improving the map view’s perceived performance. - - The default value of this property is `YES`. - */ -@property (nonatomic, assign) BOOL prefetchesTiles; - - -#pragma mark Manipulating the Viewpoint - -/** - The geographic coordinate at the center of the map view. - - Changing the value of this property centers the map on the new coordinate - without changing the current zoom level. - - Changing the value of this property updates the map view immediately. If you - want to animate the change, use the `-setCenterCoordinate:animated:` method - instead. - */ -@property (nonatomic) CLLocationCoordinate2D centerCoordinate; - -/** - Changes the center coordinate of the map and optionally animates the change. - - Changing the center coordinate centers the map on the new coordinate without - changing the current zoom level. - - @param coordinate The new center coordinate for the map. - @param animated Specify `YES` if you want the map view to scroll to the new - location or `NO` if you want the map to display the new location - immediately. - */ -- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated; - -/** - The zoom level of the receiver. - - In addition to affecting the visual size and detail of features on the map, the - zoom level affects the size of the vector tiles that are loaded. At zoom level - 0, each tile covers the entire world map; at zoom level 1, it covers ¼ of the - world; at zoom level 2, <sup>1</sup>⁄<sub>16</sub> of the world, and so on. - - Changing the value of this property updates the map view immediately. If you - want to animate the change, use the `-setZoomLevel:animated:` method instead. - */ -@property (nonatomic) double zoomLevel; - -/** - The minimum zoom level at which the map can be shown. - - Depending on the map view’s aspect ratio, the map view may be prevented from - reaching the minimum zoom level, in order to keep the map from repeating within - the current viewport. - - If the value of this property is greater than that of the `maximumZoomLevel` - property, the behavior is undefined. - - The default value of this property is 0. - */ -@property (nonatomic) IBInspectable double minimumZoomLevel; - -/** - The maximum zoom level the map can be shown at. - - If the value of this property is smaller than that of the `minimumZoomLevel` - property, the behavior is undefined. - - The default value of this property is 22. The upper bound for this property - is 25.5. - */ -@property (nonatomic) IBInspectable double maximumZoomLevel; - -/** - Changes the zoom level of the map and optionally animates the change. - - Changing the zoom level scales the map without changing the current center - coordinate. - - @param zoomLevel The new zoom level for the map. - @param animated Specify `YES` if you want the map view to animate the change - to the new zoom level or `NO` if you want the map to display the new zoom - level immediately. - */ -- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated; - -/** - The heading of the map, measured in degrees clockwise from true north. - - The value `0` means that the top edge of the map view corresponds to true - north. The value `90` means the top of the map is pointing due east. The value - `180` means the top of the map points due south, and so on. - - Changing the value of this property updates the map view immediately. If you - want to animate the change, use the `-setDirection:animated:` method instead. - */ -@property (nonatomic) CLLocationDirection direction; - -/** - Changes the heading of the map and optionally animates the change. - - Changing the heading rotates the map without changing the current center - coordinate or zoom level. - - @param direction The heading of the map, measured in degrees clockwise from - true north. - @param animated Specify `YES` if you want the map view to animate the change - to the new heading or `NO` if you want the map to display the new heading - immediately. - */ -- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated; - -/** - A camera representing the current viewpoint of the map. - */ -@property (nonatomic, copy) MGLMapCamera *camera; - -/** - Moves the viewpoint to a different location with respect to the map with an - optional transition animation. - - @param camera The new viewpoint. - @param animated Specify `YES` if you want the map view to animate the change to - the new viewpoint or `NO` if you want the map to display the new viewpoint - immediately. - */ -- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated; - -/** - Moves the viewpoint to a different location with respect to the map with an - optional transition duration and timing function. - - @param camera The new viewpoint. - @param duration The amount of time, measured in seconds, that the transition - animation should take. Specify `0` to jump to the new viewpoint - instantaneously. - @param function A timing function used for the animation. Set this parameter to - `nil` for a transition that matches most system animations. If the duration - is `0`, this parameter is ignored. - @param completion The block to execute after the animation finishes. - */ -- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion; - - /** - Moves the viewpoint to a different location with respect to the map with an - optional transition duration and timing function, and optionally some additional - padding on each side. - - @param camera The new viewpoint. - @param duration The amount of time, measured in seconds, that the transition - animation should take. Specify `0` to jump to the new viewpoint - instantaneously. - @param function A timing function used for the animation. Set this parameter to - `nil` for a transition that matches most system animations. If the duration - is `0`, this parameter is ignored. - @param edgePadding The minimum padding (in screen points) that would be visible - around the returned camera object if it were set as the receiver’s camera. - @param completion The block to execute after the animation finishes. - */ -- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(NSEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion; - - -/** - Moves the viewpoint to a different location using a transition animation that - evokes powered flight and a default duration based on the length of the flight - path. - - The transition animation seamlessly incorporates zooming and panning to help - the user find his or her bearings even after traversing a great distance. - - @param camera The new viewpoint. - @param completion The block to execute after the animation finishes. - */ -- (void)flyToCamera:(MGLMapCamera *)camera completionHandler:(nullable void (^)(void))completion; - -/** - Moves the viewpoint to a different location using a transition animation that - evokes powered flight and an optional transition duration. - - The transition animation seamlessly incorporates zooming and panning to help - the user find his or her bearings even after traversing a great distance. - - @param camera The new viewpoint. - @param duration The amount of time, measured in seconds, that the transition - animation should take. Specify `0` to jump to the new viewpoint - instantaneously. Specify a negative value to use the default duration, which - is based on the length of the flight path. - @param completion The block to execute after the animation finishes. - */ -- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration completionHandler:(nullable void (^)(void))completion; - -/** - Moves the viewpoint to a different location using a transition animation that - evokes powered flight and an optional transition duration and peak altitude. - - The transition animation seamlessly incorporates zooming and panning to help - the user find his or her bearings even after traversing a great distance. - - @param camera The new viewpoint. - @param duration The amount of time, measured in seconds, that the transition - animation should take. Specify `0` to jump to the new viewpoint - instantaneously. Specify a negative value to use the default duration, which - is based on the length of the flight path. - @param peakAltitude The altitude, measured in meters, at the midpoint of the - animation. The value of this parameter is ignored if it is negative or if - the animation transition resulting from a similar call to - `-setCamera:animated:` would have a midpoint at a higher altitude. - @param completion The block to execute after the animation finishes. - */ -- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion; - -/** - The geographic coordinate bounds visible in the receiver’s viewport. - - Changing the value of this property updates the receiver immediately. If you - want to animate the change, use the `-setVisibleCoordinateBounds:animated:` - method instead. - - If a longitude is less than −180 degrees or greater than 180 degrees, the - visible bounds straddles the antimeridian or international date line. For - example, if both Tokyo and San Francisco are visible, the visible bounds might - extend from (35.68476, −220.24257) to (37.78428, −122.41310). - */ -@property (nonatomic) MGLCoordinateBounds visibleCoordinateBounds; - -/** - Changes the receiver’s viewport to fit the given coordinate bounds, optionally - animating the change. - - To bring both sides of the antimeridian or international date line into view, - specify some longitudes less than −180 degrees or greater than 180 degrees. For - example, to show both Tokyo and San Francisco simultaneously, you could set the - visible bounds to extend from (35.68476, −220.24257) to (37.78428, −122.41310). - - @param bounds The bounds that the viewport will show in its entirety. - @param animated Specify `YES` to animate the change by smoothly scrolling and - zooming or `NO` to immediately display the given bounds. - */ -- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds animated:(BOOL)animated; - -/** - Changes the receiver’s viewport to fit the given coordinate bounds with some - additional padding on each side. - - To bring both sides of the antimeridian or international date line into view, - specify some longitudes less than −180 degrees or greater than 180 degrees. For - example, to show both Tokyo and San Francisco simultaneously, you could set the - visible bounds to extend from (35.68476, −220.24257) to (37.78428, −122.41310). - - To specify a completion handler to execute after the animation finishes, use - the `-setVisibleCoordinateBounds:edgePadding:animated:completionHandler:` method. - - @param bounds The bounds that the viewport will show in its entirety. - @param insets The minimum padding (in screen points) that will be visible - around the given coordinate bounds. - @param animated Specify `YES` to animate the change by smoothly scrolling and - zooming or `NO` to immediately display the given bounds. - */ -- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated; - -/** - Changes the receiver’s viewport to fit the given coordinate bounds with some - additional padding on each side, optionally calling a completion handler. - - To bring both sides of the antimeridian or international date line into view, - specify some longitudes less than −180 degrees or greater than 180 degrees. For - example, to show both Tokyo and San Francisco simultaneously, you could set the - visible bounds to extend from (35.68476, −220.24257) to (37.78428, −122.41310). - - @param bounds The bounds that the viewport will show in its entirety. - @param insets The minimum padding (in screen points) that will be visible - around the given coordinate bounds. - @param animated Specify `YES` to animate the change by smoothly scrolling and - zooming or `NO` to immediately display the given bounds. - @param completion The block executed after the animation finishes. - */ -- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion; - -/** - Sets the visible region so that the map displays the specified annotations. - - Calling this method updates the value in the `visibleCoordinateBounds` property - and potentially other properties to reflect the new map region. A small amount - of padding is reserved around the edges of the map view. To specify a different - amount of padding, use the `-showAnnotations:edgePadding:animated:` method. - - @param annotations The annotations that you want to be visible in the map. - @param animated `YES` if you want the map region change to be animated, or `NO` - if you want the map to display the new region immediately without animations. - */ -- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations animated:(BOOL)animated; - -/** - Sets the visible region so that the map displays the specified annotations with - the specified amount of padding on each side. - - Calling this method updates the value in the `visibleCoordinateBounds` property - and potentially other properties to reflect the new map region. - - To specify a completion handler to execute after the animation finishes, use - the `-showAnnotations:edgePadding:animated:completionHandler:` method. - - @param annotations The annotations that you want to be visible in the map. - @param insets The minimum padding (in screen points) around the edges of the - map view to keep clear of annotations. - @param animated `YES` if you want the map region change to be animated, or `NO` - if you want the map to display the new region immediately without animations. - */ -- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated; - -/** - Sets the visible region so that the map displays the specified annotations with - the specified amount of padding on each side and an optional completion - handler. - - Calling this method updates the value in the `visibleCoordinateBounds` property - and potentially other properties to reflect the new map region. - - @param annotations The annotations that you want to be visible in the map. - @param insets The minimum padding (in screen points) around the edges of the - map view to keep clear of annotations. - @param animated `YES` if you want the map region change to be animated, or `NO` - if you want the map to display the new region immediately without animations. - @param completion The block executed after the animation finishes. - */ -- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion; - -/** - Returns the camera that best fits the given coordinate bounds. - - @param bounds The coordinate bounds to fit to the receiver’s viewport. - @return A camera object centered on the same location as the coordinate bounds - with zoom level as high (close to the ground) as possible while still - including the entire coordinate bounds. The camera object uses the current - direction and pitch. - */ -- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds; - -/** - Returns the camera that best fits the given coordinate bounds with some - additional padding on each side. - - @param bounds The coordinate bounds to fit to the receiver’s viewport. - @param insets The minimum padding (in screen points) that would be visible - around the returned camera object if it were set as the receiver’s camera. - @return A camera object centered on the same location as the coordinate bounds - with zoom level as high (close to the ground) as possible while still - including the entire coordinate bounds. The camera object uses the current - direction and pitch. - */ -- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets; - -/** - Returns the camera that best fits the given coordinate bounds with some - additional padding on each side, matching an existing camera as much as - possible. - - @param camera The camera that the return camera should adhere to. All values - on this camera will be manipulated except for pitch and direction. - @param bounds The coordinate bounds to fit to the receiver’s viewport. - @param insets The minimum padding (in screen points) that would be visible - around the returned camera object if it were set as the receiver’s camera. - @return A camera object centered on the same location as the coordinate bounds - with zoom level as high (close to the ground) as possible while still - including the entire coordinate bounds. The initial camera's pitch and - direction will be honored. - */ -- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets; - -/** - Returns the camera that best fits the given shape with some additional padding - on each side, matching an existing camera as much as possible. - - @param camera The camera that the return camera should adhere to. All values - on this camera will be manipulated except for pitch and direction. - @param shape The shape to fit to the receiver’s viewport. - @param insets The minimum padding (in screen points) that would be visible - around the returned camera object if it were set as the receiver’s camera. - @return A camera object centered on the shape's center with zoom level as high - (close to the ground) as possible while still including the entire shape. - The initial camera's pitch and direction will be honored. - */ -- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingShape:(MGLShape *)shape edgePadding:(NSEdgeInsets)insets; - -/** - Returns the camera that best fits the given shape with some additional padding - on each side while looking in the specified direction. - - @param shape The shape to fit to the receiver’s viewport. - @param direction The direction of the viewport, measured in degrees clockwise - from true north. - @param insets The minimum padding (in screen points) that would be visible - around the returned camera object if it were set as the receiver’s camera. - @return A camera object centered on the shape's center with zoom level as high - (close to the ground) as possible while still including the entire shape. - The camera object uses the current pitch. - */ -- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(NSEdgeInsets)insets; - -/** - A Boolean value indicating whether the receiver automatically adjusts its - content insets. - - When the value of this property is `YES`, the map view automatically updates - its `contentInsets` property to account for any overlapping title bar or - toolbar. To overlap with the title bar or toolbar, the containing window’s - style mask must have `NSFullSizeContentViewWindowMask` set, and the title bar - must not be transparent. - - The default value of this property is `YES`. - */ -@property (nonatomic, assign) BOOL automaticallyAdjustsContentInsets; - -/** - The distance from the edges of the map view’s frame to the edges of the map - view’s logical viewport. - - When the value of this property is equal to `NSEdgeInsetsZero`, viewport - properties such as `centerCoordinate` assume a viewport that matches the map - view’s frame. Otherwise, those properties are inset, excluding part of the - frame from the viewport. For instance, if the only the top edge is inset, the - map center is effectively shifted downward. - - When the value of the `automaticallyAdjustsContentInsets` property is `YES`, - the value of this property may be overridden at any time. - - Changing the value of this property updates the map view immediately. If you - want to animate the change, use the `-setContentInset:animated:completionHandler:` - method instead. - */ -@property (nonatomic, assign) NSEdgeInsets contentInsets; - -/** - Deprecated. Sets the distance from the edges of the map view’s frame to the - edges of the map view’s logical viewport, with an optional transition animation. - - When the value of this property is equal to `NSEdgeInsetsZero`, viewport - properties such as `centerCoordinate` assume a viewport that matches the map - view’s frame. Otherwise, those properties are inset, excluding part of the - frame from the viewport. For instance, if the only the top edge is inset, the - map center is effectively shifted downward. - - When the value of the `automaticallyAdjustsContentInsets` property is `YES`, - the value of this property may be overridden at any time. - - To specify a completion handler to execute after the animation finishes, use - the `-setContentInsets:animated:completionHandler:` method. - - @param contentInsets The new values to inset the content by. - @param animated Specify `YES` if you want the map view to animate the change to - the content insets or `NO` if you want the map to inset the content - immediately. - */ -- (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated __attribute__((deprecated("Use `-setContentInsets:animated:completionHandler:` instead."))); - -/** - Sets the distance from the edges of the map view’s frame to the edges of the - map view’s logical viewport with an optional transition animation and - completion handler. - - When the value of this property is equal to `NSEdgeInsetsZero`, viewport - properties such as `centerCoordinate` assume a viewport that matches the map - view’s frame. Otherwise, those properties are inset, excluding part of the - frame from the viewport. For instance, if the only the top edge is inset, the - map center is effectively shifted downward. - - When the value of the `automaticallyAdjustsContentInsets` property is `YES`, - the value of this property may be overridden at any time. - - @param contentInsets The new values to inset the content by. - @param animated Specify `YES` if you want the map view to animate the change to - the content insets or `NO` if you want the map to inset the content - immediately. - @param completion The block executed after the animation finishes. - */ -- (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion; - -#pragma mark Configuring How the User Interacts with the Map - -/** - A Boolean value that determines whether the user may zoom the map in and out, - changing the zoom level. - - When this property is set to `YES`, the default, the user may zoom the map in - and out by pinching two fingers, by using a scroll wheel on a traditional - mouse, or by dragging the mouse cursor up and down while holding down the Shift - key. When the receiver has focus, the user may also zoom by pressing the up and - down arrow keys while holding down the Option key. - - This property controls only user interactions with the map. If you set the - value of this property to `NO`, you may still change the map zoom - programmatically. - */ -@property (nonatomic, getter=isZoomEnabled) BOOL zoomEnabled; - -/** - A Boolean value that determines whether the user may scroll around the map, - changing the center coordinate. - - When this property is set to `YES`, the default, the user may scroll the map by - swiping with two fingers or dragging the mouse cursor. When the receiver has - focus, the user may also scroll around the map by pressing the arrow keys. - - This property controls only user interactions with the map. If you set the - value of this property to `NO`, you may still change the map location - programmatically. - */ -@property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled; - -/** - A Boolean value that determines whether the user may rotate the map, changing - the direction. - - When this property is set to `YES`, the default, the user may rotate the map by - moving two fingers in a circular motion or by dragging the mouse cursor left - and right while holding down the Option key. When the receiver has focus, the - user may also zoom by pressing the left and right arrow keys while holding down - the Option key. - - This property controls only user interactions with the map. If you set the - value of this property to `NO`, you may still rotate the map programmatically. - */ -@property (nonatomic, getter=isRotateEnabled) BOOL rotateEnabled; - -/** - A Boolean value that determines whether the user may tilt of the map, changing - the pitch. - - When this property is set to `YES`, the default, the user may rotate the map by - dragging the mouse cursor up and down while holding down the Option key. - - This property controls only user interactions with the map. If you set the - value of this property to `NO`, you may still change the pitch of the map - programmatically. - */ -@property (nonatomic, getter=isPitchEnabled) BOOL pitchEnabled; - -#pragma mark Annotating the Map - -/** - The complete list of annotations associated with the receiver. (read-only) - - The objects in this array must adopt the `MGLAnnotation` protocol. If no - annotations are associated with the map view, the value of this property is - `nil`. - */ -@property (nonatomic, readonly, nullable) NSArray<id <MGLAnnotation>> *annotations; - -/** - Adds an annotation to the map view. - - @note `MGLMultiPolyline`, `MGLMultiPolygon`, and `MGLShapeCollection` objects - cannot be added to the map view at this time. Nor can `MGLMultiPoint` - objects that are not instances of `MGLPolyline` or `MGLPolygon`. Any - multipoint, multipolyline, multipolygon, or shape collection object that is - specified is silently ignored. - - @param annotation The annotation object to add to the receiver. This object - must conform to the `MGLAnnotation` protocol. The map view retains the - annotation object. - */ -- (void)addAnnotation:(id <MGLAnnotation>)annotation; - -/** - Adds an array of annotations to the map view. - - @note `MGLMultiPolyline`, `MGLMultiPolygon`, and `MGLShapeCollection` objects - cannot be added to the map view at this time. Nor can `MGLMultiPoint` - objects that are not instances of `MGLPolyline` or `MGLPolygon`. Any - multipoint, multipolyline, multipolygon, or shape collection objects that - are specified are silently ignored. - - @param annotations An array of annotation objects. Each object in the array - must conform to the `MGLAnnotation` protocol. The map view retains each - individual annotation object. - */ -- (void)addAnnotations:(NSArray<id <MGLAnnotation>> *)annotations; - -/** - The complete list of annotations associated with the receiver that are - currently visible. - - The objects in this array must adopt the `MGLAnnotation` protocol. If no - annotations are associated with the map view or if no annotations associated - with the map view are currently visible, the value of this property is `nil`. - */ -@property (nonatomic, readonly, nullable) NSArray<id <MGLAnnotation>> *visibleAnnotations; - -/** - Removes an annotation from the map view, deselecting it if it is selected. - - Removing an annotation object dissociates it from the map view entirely, - preventing it from being displayed on the map. Thus you would typically call - this method only when you want to hide or delete a given annotation. - - @param annotation The annotation object to remove. This object must conform to - the `MGLAnnotation` protocol. - */ -- (void)removeAnnotation:(id <MGLAnnotation>)annotation; - -/** - Removes an array of annotations from the map view, deselecting any selected - annotations in the array. - - Removing annotation objects dissociates them from the map view entirely, - preventing them from being displayed on the map. Thus you would typically call - this method only when you want to hide or delete the given annotations. - - @param annotations The array of annotation objects to remove. Objects in the - array must conform to the `MGLAnnotation` protocol. - */ -- (void)removeAnnotations:(NSArray<id <MGLAnnotation>> *)annotations; - -/** - Returns a reusable annotation image object associated with its identifier. - - For performance reasons, you should generally reuse `MGLAnnotationImage` - objects for identical-looking annotations in your map views. Dequeueing saves - time and memory during performance-critical operations such as scrolling. - - @param identifier A string identifying the annotation image to be reused. This - string is the same one you specify when initially returning the annotation - image object using the `-mapView:imageForAnnotation:` method. - @return An annotation image object with the given identifier, or `nil` if no - such object exists in the reuse queue. - */ -- (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier; - -/** - Returns the list of annotations associated with the receiver that intersect with - the given rectangle. - - @param rect A rectangle expressed in the map view’s coordinate system. - @return An array of objects that adopt the `MGLAnnotation` protocol or `nil` if - no annotations associated with the map view are currently visible in the - rectangle. - */ -- (nullable NSArray<id <MGLAnnotation>> *)visibleAnnotationsInRect:(CGRect)rect; - -#pragma mark Managing Annotation Selections - -/** - The currently selected annotations. - - Assigning a new array to this property selects only the first annotation in the - array. - - If the annotation is of type `MGLPointAnnotation` and is offscreen, the map is - panned so that the annotation and its callout are brought just onscreen. The - annotation is *not* centered within the viewport. - - @note In versions prior to `4.0.0` if the annotation was offscreen it was not - selected. - */ -@property (nonatomic, copy) NSArray<id <MGLAnnotation>> *selectedAnnotations; - -/** - Selects an annotation and displays a callout popover for it. - - If the annotation is of type `MGLPointAnnotation` and is offscreen, the map is - panned so that the annotation and its callout are brought just onscreen. The - annotation is *not* centered within the viewport. - - @param annotation The annotation object to select. - */ -- (void)selectAnnotation:(id <MGLAnnotation>)annotation; - -/** - Deselects an annotation and hides its callout popover. - - @param annotation The annotation object to deselect. - */ -- (void)deselectAnnotation:(nullable id <MGLAnnotation>)annotation; - -/** - A common view controller for managing a callout popover’s content view. - - Like any instance of `NSPopover`, an annotation callout manages its contents - with a view controller. The annotation object is the view controller’s - represented object. This means that you can bind controls in the view - controller’s content view to KVO-compliant properties of the annotation object, - such as `title` and `subtitle`. - - This property defines a common view controller that is used for every - annotation’s callout view. If you set this property to `nil`, a default view - controller will be used that manages a simple title label and subtitle label. - If you need distinct view controllers for different annotations, the map view’s - delegate should implement `-mapView:calloutViewControllerForAnnotation:` - instead. - */ -@property (nonatomic, strong, null_resettable) IBOutlet NSViewController *calloutViewController; - -#pragma mark Finding Annotations - -/** - Returns a point annotation located at the given point. - - @param point A point in the view’s coordinate system. - @return A point annotation whose annotation image coincides with the point. If - multiple point annotations coincide with the point, the return value is the - annotation that would be selected if the user clicks at this point. - */ -- (id <MGLAnnotation>)annotationAtPoint:(NSPoint)point; - -#pragma mark Overlaying the Map - -/** - The complete list of overlays associated with the receiver. (read-only) - - The objects in this array must adopt the `MGLOverlay` protocol. If no - overlays are associated with the map view, the value of this property is - empty array. - */ -@property (nonatomic, readonly, nonnull) NSArray<id <MGLOverlay>> *overlays; - -/** - Adds a single overlay to the map. - - To remove an overlay from a map, use the `-removeOverlay:` method. - - @param overlay The overlay object to add. This object must conform to the - `MGLOverlay` protocol. - */ -- (void)addOverlay:(id <MGLOverlay>)overlay; - -/** - Adds an array of overlays to the map. - - To remove multiple overlays from a map, use the `-removeOverlays:` method. - - @param overlays An array of objects, each of which must conform to the - `MGLOverlay` protocol. - */ -- (void)addOverlays:(NSArray<id <MGLOverlay>> *)overlays; - -/** - Removes a single overlay from the map. - - If the specified overlay is not currently associated with the map view, this - method does nothing. - - @param overlay The overlay object to remove. - */ -- (void)removeOverlay:(id <MGLOverlay>)overlay; - -/** - Removes an array of overlays from the map. - - If a given overlay object is not associated with the map view, it is ignored. - - @param overlays An array of objects, each of which conforms to the `MGLOverlay` - protocol. - */ -- (void)removeOverlays:(NSArray<id <MGLOverlay>> *)overlays; - -#pragma mark Accessing the Underlying Map Data - -/** - Returns an array of rendered map features that intersect with a given point. - - This method may return features from any of the map’s style layers. To restrict - the search to a particular layer or layers, use the - `-visibleFeaturesAtPoint:inStyleLayersWithIdentifiers:` method. For more - information about searching for map features, see that method’s documentation. - - @param point A point expressed in the map view’s coordinate system. - @return An array of objects conforming to the `MGLFeature` protocol that - represent features in the sources used by the current style. - */ -- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(NSPoint)point NS_SWIFT_NAME(visibleFeatures(at:)); - -/** - Returns an array of rendered map features that intersect with a given point, - restricted to the given style layers. - - This method returns all the intersecting features from the specified layers. To - filter the returned features, use the - `-visibleFeaturesAtPoint:inStyleLayersWithIdentifiers:predicate:` method. For - more information about searching for map features, see that method’s - documentation. - - @param point A point expressed in the map view’s coordinate system. - @param styleLayerIdentifiers A set of strings that correspond to the names of - layers defined in the current style. Only the features contained in these - layers are included in the returned array. - @return An array of objects conforming to the `MGLFeature` protocol that - represent features in the sources used by the current style. - */ -- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(NSPoint)point inStyleLayersWithIdentifiers:(nullable NSSet<NSString *> *)styleLayerIdentifiers NS_SWIFT_NAME(visibleFeatures(at:styleLayerIdentifiers:)); - -/** - Returns an array of rendered map features that intersect with a given point, - restricted to the given style layers and filtered by the given predicate. - - Each object in the returned array represents a feature rendered by the - current style and provides access to attributes specified by the relevant map - content sources. The returned array includes features loaded by - `MGLShapeSource` and `MGLVectorTileSource` objects but does not include - anything from `MGLRasterTileSource` objects, or from video or canvas sources, - which are unsupported by this SDK. - - The returned features are drawn by a style layer in the current style. For - example, suppose the current style uses the - <a href="https://www.mapbox.com/vector-tiles/mapbox-streets/">Mapbox Streets source</a>, - but none of the specified style layers includes features that have the `maki` - property set to `bus`. If you pass a point corresponding to the location of a - bus stop into this method, the bus stop feature does not appear in the - resulting array. On the other hand, if the style does include bus stops, an - `MGLFeature` object representing that bus stop is returned and its - `attributes` dictionary has the `maki` key set to `bus` (along with other - attributes). The dictionary contains only the attributes provided by the - tile source; it does not include computed attribute values or rules about how - the feature is rendered by the current style. - - The returned array is sorted by z-order, starting with the topmost rendered - feature and ending with the bottommost rendered feature. A feature that is - rendered multiple times due to wrapping across the antimeridian at low zoom - levels is included only once, subject to the caveat that follows. - - Features come from tiled vector data or GeoJSON data that is converted to tiles - internally, so feature geometries are clipped at tile boundaries and features - may appear duplicated across tiles. For example, suppose the specified point - lies along a road that spans the screen. The resulting array includes those - parts of the road that lie within the map tile that contain the specified - point, even if the road extends into other tiles. - - To find out the layer names in a particular style, view the style in - <a href="https://www.mapbox.com/studio/">Mapbox Studio</a>. - - Only visible features are returned. To obtain features regardless of - visibility, use the - `-[MGLVectorTileSource featuresInSourceLayersWithIdentifiers:predicate:]` and - `-[MGLShapeSource featuresMatchingPredicate:]` methods on the relevant sources. - - @note Layer identifiers are not guaranteed to exist across styles or different - versions of the same style. Applications that use this API must first set - the style URL to an explicitly versioned style using a convenience method - like `+[MGLStyle outdoorsStyleURLWithVersion:]`, `MGLMapView`’s “Style URL” - inspectable in Interface Builder, or a manually constructed `NSURL`. This - approach also avoids layer identifer name changes that will occur in the - default style’s layers over time. - - @param point A point expressed in the map view’s coordinate system. - @param styleLayerIdentifiers A set of strings that correspond to the names of - layers defined in the current style. Only the features contained in these - layers are included in the returned array. - @param predicate A predicate to filter the returned features. - @return An array of objects conforming to the `MGLFeature` protocol that - represent features in the sources used by the current style. - */ -- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(NSPoint)point inStyleLayersWithIdentifiers:(nullable NSSet<NSString *> *)styleLayerIdentifiers predicate:(nullable NSPredicate *)predicate NS_SWIFT_NAME(visibleFeatures(at:styleLayerIdentifiers:predicate:)); - -/** - Returns an array of rendered map features that intersect with the given - rectangle. - - This method may return features from any of the map’s style layers. To restrict - the search to a particular layer or layers, use the - `-visibleFeaturesAtPoint:inStyleLayersWithIdentifiers:` method. For more - information about searching for map features, see that method’s documentation. - - @param rect A rectangle expressed in the map view’s coordinate system. - @return An array of objects conforming to the `MGLFeature` protocol that - represent features in the sources used by the current style. - */ -- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(NSRect)rect NS_SWIFT_NAME(visibleFeatures(in:)); - -/** - Returns an array of rendered map features that intersect with the given - rectangle, restricted to the given style layers. - - This method returns all the intersecting features from the specified layers. To - filter the returned features, use the - `-visibleFeaturesAtPoint:inStyleLayersWithIdentifiers:predicate:` method. For - more information about searching for map features, see that method’s - documentation. - - @param rect A rectangle expressed in the map view’s coordinate system. - @param styleLayerIdentifiers A set of strings that correspond to the names of - layers defined in the current style. Only the features contained in these - layers are included in the returned array. - @return An array of objects conforming to the `MGLFeature` protocol that - represent features in the sources used by the current style. - */ -- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(NSRect)rect inStyleLayersWithIdentifiers:(nullable NSSet<NSString *> *)styleLayerIdentifiers NS_SWIFT_NAME(visibleFeatures(at:styleLayerIdentifiers:)); - -/** - Returns an array of rendered map features that intersect with the given - rectangle, restricted to the given style layers and filtered by the given - predicate. - - Each object in the returned array represents a feature rendered by the - current style and provides access to attributes specified by the relevant map - content sources. The returned array includes features loaded by - `MGLShapeSource` and `MGLVectorTileSource` objects but does not include - anything from `MGLRasterTileSource` objects, or from video or canvas sources, - which are unsupported by this SDK. - - The returned features are drawn by a style layer in the current style. For - example, suppose the current style uses the - <a href="https://www.mapbox.com/vector-tiles/mapbox-streets/">Mapbox Streets source</a>, - but none of the specified style layers includes features that have the `maki` - property set to `bus`. If you pass a rectangle containing the location of a bus - stop into this method, the bus stop feature does not appear in the resulting - array. On the other hand, if the style does include bus stops, an `MGLFeature` - object representing that bus stop is returned and its `attributes` dictionary - has the `maki` key set to `bus` (along with other attributes). The dictionary - contains only the attributes provided by the tile source; it does not include - computed attribute values or rules about how the feature is rendered by the - current style. - - The returned array is sorted by z-order, starting with the topmost rendered - feature and ending with the bottommost rendered feature. A feature that is - rendered multiple times due to wrapping across the antimeridian at low zoom - levels is included only once, subject to the caveat that follows. - - Features come from tiled vector data or GeoJSON data that is converted to tiles - internally, so feature geometries are clipped at tile boundaries and features - may appear duplicated across tiles. For example, suppose the specified - rectangle intersects with a road that spans the screen. The resulting array - includes those parts of the road that lie within the map tiles covering the - specified rectangle, even if the road extends into other tiles. The portion of - the road within each map tile is included individually. - - To find out the layer names in a particular style, view the style in - <a href="https://www.mapbox.com/studio/">Mapbox Studio</a>. - - Only visible features are returned. To obtain features regardless of - visibility, use the - `-[MGLVectorTileSource featuresInSourceLayersWithIdentifiers:predicate:]` and - `-[MGLShapeSource featuresMatchingPredicate:]` methods on the relevant sources. - - @note Layer identifiers are not guaranteed to exist across styles or different - versions of the same style. Applications that use this API must first set - the style URL to an explicitly versioned style using a convenience method - like `+[MGLStyle outdoorsStyleURLWithVersion:]`, `MGLMapView`’s “Style URL” - inspectable in Interface Builder, or a manually constructed `NSURL`. This - approach also avoids layer identifer name changes that will occur in the - default style’s layers over time. - - @param rect A rectangle expressed in the map view’s coordinate system. - @param styleLayerIdentifiers A set of strings that correspond to the names of - layers defined in the current style. Only the features contained in these - layers are included in the returned array. - @param predicate A predicate to filter the returned features. - @return An array of objects conforming to the `MGLFeature` protocol that - represent features in the sources used by the current style. - */ -- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(NSRect)rect inStyleLayersWithIdentifiers:(nullable NSSet<NSString *> *)styleLayerIdentifiers predicate:(nullable NSPredicate *)predicate NS_SWIFT_NAME(visibleFeatures(in:styleLayerIdentifiers:predicate:)); - -#pragma mark Converting Geographic Coordinates - -/** - Converts a geographic coordinate to a point in the given view’s coordinate - system. - - @param coordinate The geographic coordinate to convert. - @param view The view in whose coordinate system the returned point should be - expressed. If this parameter is `nil`, the returned point is expressed in - the window’s coordinate system. If `view` is not `nil`, it must belong to - the same window as the map view. - @return The point (in the appropriate view or window coordinate system) - corresponding to the given geographic coordinate. - */ -- (NSPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable NSView *)view; - -/** - Converts a point in the given view’s coordinate system to a geographic - coordinate. - - @param point The point to convert. - @param view The view in whose coordinate system the point is expressed. - @return The geographic coordinate at the given point. - */ -- (CLLocationCoordinate2D)convertPoint:(NSPoint)point toCoordinateFromView:(nullable NSView *)view; - -/** - Converts a geographic bounding box to a rectangle in the given view’s - coordinate system. - - To bring both sides of the antimeridian or international date line into view, - specify some longitudes less than −180 degrees or greater than 180 degrees. For - example, to show both Tokyo and San Francisco simultaneously, you could set the - visible bounds to extend from (35.68476, −220.24257) to (37.78428, −122.41310). - - @param bounds The geographic bounding box to convert. - @param view The view in whose coordinate system the returned rectangle should - be expressed. If this parameter is `nil`, the returned rectangle is - expressed in the window’s coordinate system. If `view` is not `nil`, it must - belong to the same window as the map view. - */ -- (NSRect)convertCoordinateBounds:(MGLCoordinateBounds)bounds toRectToView:(nullable NSView *)view; - -/** - Converts a rectangle in the given view’s coordinate system to a geographic - bounding box. - - If a longitude is less than −180 degrees or greater than 180 degrees, the - bounding box straddles the antimeridian or international date line. - - @param rect The rectangle to convert. - @param view The view in whose coordinate system the rectangle is expressed. - @return The geographic bounding box coextensive with the given rectangle. - */ -- (MGLCoordinateBounds)convertRect:(NSRect)rect toCoordinateBoundsFromView:(nullable NSView *)view; - -/** - Returns the distance spanned by one point in the map view’s coordinate system - at the given latitude and current zoom level. - - The distance between points decreases as the latitude approaches the poles. - This relationship parallels the relationship between longitudinal coordinates - at different latitudes. - - @param latitude The latitude of the geographic coordinate represented by the - point. - @return The distance in meters spanned by a single point. - */ -- (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude; - -#pragma mark Giving Feedback to Improve the Map - -/** - Opens one or more webpages in the default Web browser in which the user can - provide feedback about the map data. - - You should add a menu item to the Help menu of your application that invokes - this method. Title it “Improve This Map” or similar. Set its target to the - first responder and its action to `giveFeedback:`. - - This map view searches the current style’s sources for webpages to open. - Specifically, each source’s tile set has an `attribution` property containing - HTML code; if an <code><a></code> tag (link) within that code has an - <code>class</code> attribute set to <code>mapbox-improve-map</code>, its - <code>href</code> attribute defines the URL to open. Such links are omitted - from the attribution view. - */ -- (IBAction)giveFeedback:(id)sender; - -#pragma mark Debugging the Map - -/** - The options that determine which debugging aids are shown on the map. - - These options are all disabled by default and should remain disabled in - released software for performance and aesthetic reasons. - */ -@property (nonatomic) MGLMapDebugMaskOptions debugMask; - -@end - -NS_ASSUME_NONNULL_END diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm deleted file mode 100644 index aec9cea0bc..0000000000 --- a/platform/macos/src/MGLMapView.mm +++ /dev/null @@ -1,3081 +0,0 @@ -#import "MGLMapView_Private.h" - -#import "MGLAttributionButton.h" -#import "MGLCompassCell.h" -#import "MGLStyle.h" -#import "MGLRendererFrontend.h" -#import "MGLRendererConfiguration.h" - -#import "MGLAnnotationImage_Private.h" -#import "MGLAttributionInfo_Private.h" -#import "MGLFeature_Private.h" -#import "MGLFoundation_Private.h" -#import "MGLGeometry_Private.h" -#import "MGLMultiPoint_Private.h" -#import "MGLOfflineStorage_Private.h" -#import "MGLStyle_Private.h" -#import "MGLShape_Private.h" - -#import "MGLAccountManager.h" -#import "MGLMapCamera.h" -#import "MGLPolygon.h" -#import "MGLPolyline.h" -#import "MGLAnnotationImage.h" -#import "MGLMapViewDelegate.h" -#import "MGLImageSource.h" - -#import <mbgl/map/map.hpp> -#import <mbgl/map/map_options.hpp> -#import <mbgl/style/style.hpp> -#import <mbgl/annotation/annotation.hpp> -#import <mbgl/map/camera.hpp> -#import <mbgl/storage/reachability.h> -#import <mbgl/style/image.hpp> -#import <mbgl/renderer/renderer.hpp> -#import <mbgl/storage/network_status.hpp> -#import <mbgl/storage/resource_options.hpp> -#import <mbgl/math/wrap.hpp> -#import <mbgl/util/constants.hpp> -#import <mbgl/util/chrono.hpp> -#import <mbgl/util/exception.hpp> -#import <mbgl/util/run_loop.hpp> -#import <mbgl/util/string.hpp> -#import <mbgl/util/projection.hpp> - -#import <map> -#import <unordered_map> -#import <unordered_set> - -#import "MGLMapView+Impl.h" -#import "NSBundle+MGLAdditions.h" -#import "NSDate+MGLAdditions.h" -#import "NSProcessInfo+MGLAdditions.h" -#import "NSException+MGLAdditions.h" -#import "NSString+MGLAdditions.h" -#import "NSURL+MGLAdditions.h" -#import "NSColor+MGLAdditions.h" -#import "NSImage+MGLAdditions.h" -#import "NSPredicate+MGLPrivateAdditions.h" -#import "MGLLoggingConfiguration_Private.h" -#import "MGLNetworkIntegrationManager.h" - -class MGLAnnotationContext; - -/// Distance from the edge of the view to ornament views (logo, attribution, etc.). -const CGFloat MGLOrnamentPadding = 12; - -/// Alpha value of the ornament views (logo, attribution, etc.). -const CGFloat MGLOrnamentOpacity = 0.9; - -/// Default duration for programmatic animations. -const NSTimeInterval MGLAnimationDuration = 0.3; - -/// Distance in points that a single press of the panning keyboard shortcut pans the map by. -const CGFloat MGLKeyPanningIncrement = 150; - -/// Degrees that a single press of the rotation keyboard shortcut rotates the map by. -const CLLocationDegrees MGLKeyRotationIncrement = 25; - -/// Key for the user default that, when true, causes the map view to zoom in and out on scroll wheel events. -NSString * const MGLScrollWheelZoomsMapViewDefaultKey = @"MGLScrollWheelZoomsMapView"; - -/// Reuse identifier and file name of the default point annotation image. -static NSString * const MGLDefaultStyleMarkerSymbolName = @"default_marker"; - -/// Prefix that denotes a sprite installed by MGLMapView, to avoid collisions -/// with style-defined sprites. -static NSString * const MGLAnnotationSpritePrefix = @"com.mapbox.sprites."; - -/// Slop area around the hit testing point, allowing for imprecise annotation selection. -const CGFloat MGLAnnotationImagePaddingForHitTest = 4; - -/// Distance from the callout’s anchor point to the annotation it points to. -const CGFloat MGLAnnotationImagePaddingForCallout = 4; - -/// Padding to edge of view that an offscreen annotation must have when being brought onscreen (by being selected) -const NSEdgeInsets MGLMapViewOffscreenAnnotationPadding = NSEdgeInsetsMake(-30.0f, -30.0f, -30.0f, -30.0f); - -/// Unique identifier representing a single annotation in mbgl. -typedef uint64_t MGLAnnotationTag; - -/// An indication that the requested annotation was not found or is nonexistent. -enum { MGLAnnotationTagNotFound = UINT64_MAX }; - -/// Mapping from an annotation tag to metadata about that annotation, including -/// the annotation itself. -typedef std::unordered_map<MGLAnnotationTag, MGLAnnotationContext> MGLAnnotationTagContextMap; - -/// Mapping from an annotation object to an annotation tag. -typedef std::map<id<MGLAnnotation>, MGLAnnotationTag> MGLAnnotationObjectTagMap; - -/// Returns an NSImage for the default marker image. -NSImage *MGLDefaultMarkerImage() { - NSString *path = [[NSBundle mgl_frameworkBundle] pathForResource:MGLDefaultStyleMarkerSymbolName - ofType:@"pdf"]; - return [[NSImage alloc] initWithContentsOfFile:path]; -} - -/// Converts a media timing function into a unit bezier object usable in mbgl. -mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function) { - if (!function) { - function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; - } - float p1[2], p2[2]; - [function getControlPointAtIndex:0 values:p1]; - [function getControlPointAtIndex:1 values:p2]; - return { p1[0], p1[1], p2[0], p2[1] }; -} - -/// Lightweight container for metadata about an annotation, including the annotation itself. -class MGLAnnotationContext { -public: - id <MGLAnnotation> annotation; - /// The annotation’s image’s reuse identifier. - NSString *imageReuseIdentifier; -}; - -@interface MGLMapView () <NSPopoverDelegate, MGLMultiPointDelegate, NSGestureRecognizerDelegate> - -@property (nonatomic, readwrite) NSSegmentedControl *zoomControls; -@property (nonatomic, readwrite) NSSlider *compass; -@property (nonatomic, readwrite) NSImageView *logoView; -@property (nonatomic, readwrite) NSView *attributionView; - -@property (nonatomic, readwrite) MGLStyle *style; - -/// Mapping from reusable identifiers to annotation images. -@property (nonatomic) NSMutableDictionary<NSString *, MGLAnnotationImage *> *annotationImagesByIdentifier; -/// Currently shown popover representing the selected annotation. -@property (nonatomic) NSPopover *calloutForSelectedAnnotation; - -@property (nonatomic, readwrite, getter=isDormant) BOOL dormant; - -@end - -@implementation MGLMapView { - /// Cross-platform map view controller. - mbgl::Map *_mbglMap; - std::unique_ptr<MGLMapViewImpl> _mbglView; - std::unique_ptr<MGLRenderFrontend> _rendererFrontend; - - NSPanGestureRecognizer *_panGestureRecognizer; - NSMagnificationGestureRecognizer *_magnificationGestureRecognizer; - NSRotationGestureRecognizer *_rotationGestureRecognizer; - NSClickGestureRecognizer *_singleClickRecognizer; - double _zoomAtBeginningOfGesture; - CLLocationDirection _directionAtBeginningOfGesture; - CGFloat _pitchAtBeginningOfGesture; - BOOL _didHideCursorDuringGesture; - - MGLAnnotationTagContextMap _annotationContextsByAnnotationTag; - MGLAnnotationObjectTagMap _annotationTagsByAnnotation; - MGLAnnotationTag _selectedAnnotationTag; - MGLAnnotationTag _lastSelectedAnnotationTag; - /// Size of the rectangle formed by unioning the maximum slop area around every annotation image. - NSSize _unionedAnnotationImageSize; - std::vector<MGLAnnotationTag> _annotationsNearbyLastClick; - /// True if any annotations that have tooltips have been installed. - BOOL _wantsToolTipRects; - /// True if any annotation images that have custom cursors have been installed. - BOOL _wantsCursorRects; - /// True if a willChange notification has been issued for shape annotation layers and a didChange notification is pending. - BOOL _isChangingAnnotationLayers; - - // Cached checks for delegate method implementations that may be called from - // MGLMultiPointDelegate methods. - - BOOL _delegateHasAlphasForShapeAnnotations; - BOOL _delegateHasStrokeColorsForShapeAnnotations; - BOOL _delegateHasFillColorsForShapeAnnotations; - BOOL _delegateHasLineWidthsForShapeAnnotations; - - /// True if the current process is the Interface Builder designable - /// renderer. When drawing the designable, the map is paused, so any call to - /// it may hang the process. - BOOL _isTargetingInterfaceBuilder; - CLLocationDegrees _pendingLatitude; - CLLocationDegrees _pendingLongitude; - - /// True if the view is currently printing itself. - BOOL _isPrinting; - - /// reachability instance - MGLReachability *_reachability; -} - -#pragma mark Lifecycle - -+ (void)initialize { - if (self == [MGLMapView class]) { - [[NSUserDefaults standardUserDefaults] registerDefaults:@{ - MGLScrollWheelZoomsMapViewDefaultKey: @NO, - }]; - } -} - -- (instancetype)initWithFrame:(NSRect)frameRect { - if (self = [super initWithFrame:frameRect]) { - MGLLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - MGLLogDebug(@"Initializing frame: %@", NSStringFromRect(frameRect)); - [self commonInit]; - self.styleURL = nil; - MGLLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); - } - return self; -} - -- (instancetype)initWithFrame:(NSRect)frame styleURL:(nullable NSURL *)styleURL { - if (self = [super initWithFrame:frame]) { - MGLLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - MGLLogDebug(@"Initializing frame: %@ styleURL: %@", NSStringFromRect(frame), styleURL); - [self commonInit]; - self.styleURL = styleURL; - MGLLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); - } - return self; -} - -- (instancetype)initWithCoder:(nonnull NSCoder *)decoder { - if (self = [super initWithCoder:decoder]) { - MGLLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - [self commonInit]; - MGLLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); - } - return self; -} - -- (void)awakeFromNib { - [super awakeFromNib]; - - // If the Style URL inspectable was not set, make sure to go through - // -setStyleURL: to load the default style. - if (_mbglMap->getStyle().getURL().empty()) { - self.styleURL = nil; - } -} - -+ (NSArray *)restorableStateKeyPaths { - return @[@"camera", @"debugMask"]; -} - -- (void)commonInit { - MGLNativeNetworkManager.sharedManager.delegate = MGLNetworkIntegrationManager.sharedManager; - _isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent; - - // Set up cross-platform controllers and resources. - _mbglView = MGLMapViewImpl::Create(self); - - // Delete the pre-offline ambient cache at - // ~/Library/Caches/com.mapbox.MapboxGL/cache.db. - NSURL *cachesDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory - inDomain:NSUserDomainMask - appropriateForURL:nil - create:NO - error:nil]; - cachesDirectoryURL = [cachesDirectoryURL URLByAppendingPathComponent:@"com.mapbox.MapboxGL"]; - NSURL *legacyCacheURL = [cachesDirectoryURL URLByAppendingPathComponent:@"cache.db"]; - [[NSFileManager defaultManager] removeItemAtURL:legacyCacheURL error:NULL]; - - MGLRendererConfiguration *config = [MGLRendererConfiguration currentConfiguration]; - - auto renderer = std::make_unique<mbgl::Renderer>(_mbglView->getRendererBackend(), config.scaleFactor, config.localFontFamilyName); - BOOL enableCrossSourceCollisions = !config.perSourceCollisions; - _rendererFrontend = std::make_unique<MGLRenderFrontend>(std::move(renderer), self, _mbglView->getRendererBackend(), true); - - mbgl::MapOptions mapOptions; - mapOptions.withMapMode(mbgl::MapMode::Continuous) - .withSize(self.size) - .withPixelRatio(config.scaleFactor) - .withConstrainMode(mbgl::ConstrainMode::None) - .withViewportMode(mbgl::ViewportMode::Default) - .withCrossSourceCollisions(enableCrossSourceCollisions); - - mbgl::ResourceOptions resourceOptions; - resourceOptions.withCachePath([[MGLOfflineStorage sharedOfflineStorage] mbglCachePath]) - .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String); - - _mbglMap = new mbgl::Map(*_rendererFrontend, *_mbglView, mapOptions, resourceOptions); - - // Notify map object when network reachability status changes. - _reachability = [MGLReachability reachabilityForInternetConnection]; - _reachability.reachableBlock = ^(MGLReachability *) { - mbgl::NetworkStatus::Reachable(); - }; - [_reachability startNotifier]; - - // Install ornaments and gesture recognizers. - [self installZoomControls]; - [self installCompass]; - [self installLogoView]; - [self installAttributionView]; - [self installGestureRecognizers]; - - // Set up annotation management and selection state. - _annotationImagesByIdentifier = [NSMutableDictionary dictionary]; - _annotationContextsByAnnotationTag = {}; - _annotationTagsByAnnotation = {}; - _selectedAnnotationTag = MGLAnnotationTagNotFound; - _lastSelectedAnnotationTag = MGLAnnotationTagNotFound; - _annotationsNearbyLastClick = {}; - - // Jump to Null Island initially. - self.automaticallyAdjustsContentInsets = YES; - mbgl::CameraOptions options; - options.center = mbgl::LatLng(0, 0); - options.padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); - options.zoom = *_mbglMap->getBounds().minZoom; - _mbglMap->jumpTo(options); - _pendingLatitude = NAN; - _pendingLongitude = NAN; -} - -- (mbgl::Size)size { - // check for minimum texture size supported by OpenGL ES 2.0 - // - CGSize size = CGSizeMake(MAX(self.bounds.size.width, 64), MAX(self.bounds.size.height, 64)); - return { static_cast<uint32_t>(size.width), - static_cast<uint32_t>(size.height) }; -} - -- (mbgl::Size)framebufferSize { - NSRect bounds = [self convertRectToBacking:self.bounds]; - return { static_cast<uint32_t>(bounds.size.width), static_cast<uint32_t>(bounds.size.height) }; -} - -/// Adds zoom controls to the lower-right corner. -- (void)installZoomControls { - _zoomControls = [[NSSegmentedControl alloc] initWithFrame:NSZeroRect]; - _zoomControls.wantsLayer = YES; - _zoomControls.layer.opacity = MGLOrnamentOpacity; - [(NSSegmentedCell *)_zoomControls.cell setTrackingMode:NSSegmentSwitchTrackingMomentary]; - _zoomControls.continuous = YES; - _zoomControls.segmentCount = 2; - [_zoomControls setLabel:NSLocalizedStringWithDefaultValue(@"ZOOM_OUT_LABEL", nil, nil, @"−", @"Label of Zoom Out button; U+2212 MINUS SIGN") forSegment:0]; - [(NSSegmentedCell *)_zoomControls.cell setTag:0 forSegment:0]; - [(NSSegmentedCell *)_zoomControls.cell setToolTip:NSLocalizedStringWithDefaultValue(@"ZOOM_OUT_TOOLTIP", nil, nil, @"Zoom Out", @"Tooltip of Zoom Out button") forSegment:0]; - [_zoomControls setLabel:NSLocalizedStringWithDefaultValue(@"ZOOM_IN_LABEL", nil, nil, @"+", @"Label of Zoom In button") forSegment:1]; - [(NSSegmentedCell *)_zoomControls.cell setTag:1 forSegment:1]; - [(NSSegmentedCell *)_zoomControls.cell setToolTip:NSLocalizedStringWithDefaultValue(@"ZOOM_IN_TOOLTIP", nil, nil, @"Zoom In", @"Tooltip of Zoom In button") forSegment:1]; - _zoomControls.target = self; - _zoomControls.action = @selector(zoomInOrOut:); - _zoomControls.controlSize = NSRegularControlSize; - [_zoomControls sizeToFit]; - _zoomControls.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_zoomControls]; -} - -/// Adds a rudimentary compass control to the lower-right corner. -- (void)installCompass { - _compass = [[NSSlider alloc] initWithFrame:NSZeroRect]; - _compass.wantsLayer = YES; - _compass.layer.opacity = MGLOrnamentOpacity; - _compass.cell = [[MGLCompassCell alloc] init]; - _compass.continuous = YES; - _compass.target = self; - _compass.action = @selector(rotate:); - [_compass sizeToFit]; - _compass.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_compass]; -} - -/// Adds a Mapbox logo to the lower-left corner. -- (void)installLogoView { - _logoView = [[NSImageView alloc] initWithFrame:NSZeroRect]; - _logoView.wantsLayer = YES; - NSImage *logoImage = [[NSImage alloc] initWithContentsOfFile: - [[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]]; - // Account for the image’s built-in padding when aligning other controls to the logo. - logoImage.alignmentRect = NSOffsetRect(logoImage.alignmentRect, 0, 3); - _logoView.image = logoImage; - _logoView.translatesAutoresizingMaskIntoConstraints = NO; - _logoView.accessibilityTitle = NSLocalizedStringWithDefaultValue(@"MAP_A11Y_TITLE", nil, nil, @"Mapbox", @"Accessibility title"); - [self addSubview:_logoView]; -} - -/// Adds legally required map attribution to the lower-left corner. -- (void)installAttributionView { - [_attributionView removeFromSuperview]; - _attributionView = [[NSView alloc] initWithFrame:NSZeroRect]; - _attributionView.wantsLayer = YES; - - // Make the background and foreground translucent to be unobtrusive. - _attributionView.layer.opacity = 0.6; - - // Blur the background to prevent text underneath the view from running into - // the text in the view, rendering it illegible. - CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; - [attributionBlurFilter setDefaults]; - - // Brighten the background. This is similar to applying a translucent white - // background on the view, but the effect is a bit more subtle and works - // well with the blur above. - CIFilter *attributionColorFilter = [CIFilter filterWithName:@"CIColorControls"]; - [attributionColorFilter setDefaults]; - [attributionColorFilter setValue:@(0.1) forKey:kCIInputBrightnessKey]; - - // Apply the background effects and a standard button corner radius. - _attributionView.backgroundFilters = @[attributionColorFilter, attributionBlurFilter]; - _attributionView.layer.cornerRadius = 4; - - _attributionView.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_attributionView]; - [self updateAttributionView]; -} - -/// Adds gesture recognizers for manipulating the viewport and selecting annotations. -- (void)installGestureRecognizers { - _scrollEnabled = YES; - _zoomEnabled = YES; - _rotateEnabled = YES; - _pitchEnabled = YES; - - _panGestureRecognizer = [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; - _panGestureRecognizer.delaysKeyEvents = YES; - [self addGestureRecognizer:_panGestureRecognizer]; - - _singleClickRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleClickGesture:)]; - _singleClickRecognizer.delaysPrimaryMouseButtonEvents = NO; - _singleClickRecognizer.delegate = self; - [self addGestureRecognizer:_singleClickRecognizer]; - - NSClickGestureRecognizer *rightClickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleRightClickGesture:)]; - rightClickGestureRecognizer.buttonMask = 0x2; - [self addGestureRecognizer:rightClickGestureRecognizer]; - - NSClickGestureRecognizer *doubleClickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleClickGesture:)]; - doubleClickGestureRecognizer.numberOfClicksRequired = 2; - doubleClickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO; - [self addGestureRecognizer:doubleClickGestureRecognizer]; - - _magnificationGestureRecognizer = [[NSMagnificationGestureRecognizer alloc] initWithTarget:self action:@selector(handleMagnificationGesture:)]; - [self addGestureRecognizer:_magnificationGestureRecognizer]; - - _rotationGestureRecognizer = [[NSRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotationGesture:)]; - [self addGestureRecognizer:_rotationGestureRecognizer]; -} - -/// Updates the attribution view to reflect the sources used. For now, this is -/// hard-coded to the standard Mapbox and OpenStreetMap attribution. -- (void)updateAttributionView { - NSView *attributionView = self.attributionView; - for (NSView *button in attributionView.subviews) { - [button removeConstraints:button.constraints]; - } - attributionView.subviews = @[]; - [attributionView removeConstraints:attributionView.constraints]; - - // Make the whole string mini by default. - // Force links to be black, because the default blue is distracting. - CGFloat miniSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize]; - NSArray *attributionInfos = [self.style attributionInfosWithFontSize:miniSize linkColor:[NSColor blackColor]]; - for (MGLAttributionInfo *info in attributionInfos) { - // Feedback links are added to the Help menu. - if (info.feedbackLink) { - continue; - } - - // For each attribution, add a borderless button that responds to clicks - // and feels like a hyperlink. - NSButton *button = [[MGLAttributionButton alloc] initWithAttributionInfo:info]; - button.controlSize = NSMiniControlSize; - button.translatesAutoresizingMaskIntoConstraints = NO; - - // Set the new button flush with the buttom of the container and to the - // right of the previous button, with standard spacing. If there is no - // previous button, align to the container instead. - NSView *previousView = attributionView.subviews.lastObject; - [attributionView addSubview:button]; - [attributionView addConstraint: - [NSLayoutConstraint constraintWithItem:button - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:attributionView - attribute:NSLayoutAttributeBottom - multiplier:1 - constant:0]]; - [attributionView addConstraint: - [NSLayoutConstraint constraintWithItem:button - attribute:NSLayoutAttributeLeading - relatedBy:NSLayoutRelationEqual - toItem:previousView ? previousView : attributionView - attribute:previousView ? NSLayoutAttributeTrailing : NSLayoutAttributeLeading - multiplier:1 - constant:8]]; - [attributionView addConstraint: - [NSLayoutConstraint constraintWithItem:button - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:attributionView - attribute:NSLayoutAttributeTop - multiplier:1 - constant:0]]; - } - - if (attributionInfos.count) { - [attributionView addConstraint: - [NSLayoutConstraint constraintWithItem:attributionView - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:attributionView.subviews.lastObject - attribute:NSLayoutAttributeTrailing - multiplier:1 - constant:8]]; - } -} - -- (void)dealloc { - - [_reachability stopNotifier]; - - - [self.window removeObserver:self forKeyPath:@"contentLayoutRect"]; - [self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"]; - - // Close any annotation callout immediately. - [self.calloutForSelectedAnnotation close]; - self.calloutForSelectedAnnotation = nil; - - // Removing the annotations unregisters any outstanding KVO observers. - [self removeAnnotations:self.annotations]; - - if (_mbglMap) { - delete _mbglMap; - _mbglMap = nullptr; - } - _mbglView.reset(); -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(__unused NSDictionary *)change context:(void *)context { - if ([keyPath isEqualToString:@"contentLayoutRect"] || - [keyPath isEqualToString:@"titlebarAppearsTransparent"]) { - [self adjustContentInsets]; - } else if ([keyPath isEqualToString:@"coordinate"] && - [object conformsToProtocol:@protocol(MGLAnnotation)] && - ![object isKindOfClass:[MGLMultiPoint class]]) { - id <MGLAnnotation> annotation = object; - MGLAnnotationTag annotationTag = (MGLAnnotationTag)(NSUInteger)context; - // We can get here because a subclass registered itself as an observer - // of the coordinate key path of a non-multipoint annotation but failed - // to handle the change. This check deters us from treating the - // subclass’s context as an annotation tag. If the context happens to - // match a valid annotation tag, the annotation will be unnecessarily - // but safely updated. - if (annotation == [self annotationWithTag:annotationTag]) { - const mbgl::Point<double> point = MGLPointFromLocationCoordinate2D(annotation.coordinate); - MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - _mbglMap->updateAnnotation(annotationTag, mbgl::SymbolAnnotation { point, annotationImage.styleIconIdentifier.UTF8String ?: "" }); - [self updateAnnotationCallouts]; - } - } else if ([keyPath isEqualToString:@"coordinates"] && - [object isKindOfClass:[MGLMultiPoint class]]) { - MGLMultiPoint *annotation = object; - MGLAnnotationTag annotationTag = (MGLAnnotationTag)(NSUInteger)context; - // We can get here because a subclass registered itself as an observer - // of the coordinates key path of a multipoint annotation but failed - // to handle the change. This check deters us from treating the - // subclass’s context as an annotation tag. If the context happens to - // match a valid annotation tag, the annotation will be unnecessarily - // but safely updated. - if (annotation == [self annotationWithTag:annotationTag]) { - _mbglMap->updateAnnotation(annotationTag, [annotation annotationObjectWithDelegate:self]); - [self updateAnnotationCallouts]; - } - } -} - -+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { - return [key isEqualToString:@"annotations"] ? YES : [super automaticallyNotifiesObserversForKey:key]; -} - -- (void)setDelegate:(id<MGLMapViewDelegate>)delegate { - _delegate = delegate; - - // Cache checks for delegate method implementations that may be called in a - // hot loop, namely the annotation style methods. - _delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)]; - _delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)]; - _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)]; - _delegateHasLineWidthsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)]; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wundeclared-selector" - if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) { - NSLog(@"-mapView:regionWillChangeAnimated: is not supported by the macOS SDK, but %@ implements it anyways. " - @"Please implement -[%@ mapView:cameraWillChangeAnimated:] instead.", - NSStringFromClass([delegate class]), NSStringFromClass([delegate class])); - } - if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) { - NSLog(@"-mapViewRegionIsChanging: is not supported by the macOS SDK, but %@ implements it anyways. " - @"Please implement -[%@ mapViewCameraIsChanging:] instead.", - NSStringFromClass([delegate class]), NSStringFromClass([delegate class])); - } - if ([self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]) { - NSLog(@"-mapView:regionDidChangeAnimated: is not supported by the macOS SDK, but %@ implements it anyways. " - @"Please implement -[%@ mapView:cameraDidChangeAnimated:] instead.", - NSStringFromClass([delegate class]), NSStringFromClass([delegate class])); - } -#pragma clang diagnostic pop -} - -#pragma mark Style - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingStyle { - return [NSSet setWithObject:@"styleURL"]; -} - -- (nonnull NSURL *)styleURL { - NSString *styleURLString = @(_mbglMap->getStyle().getURL().c_str()).mgl_stringOrNilIfEmpty; - return styleURLString ? [NSURL URLWithString:styleURLString] : [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion]; -} - -- (void)setStyleURL:(nullable NSURL *)styleURL { - if (_isTargetingInterfaceBuilder) { - return; - } - - // Default to Streets. - if (!styleURL) { - styleURL = [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion]; - } - MGLLogDebug(@"Setting styleURL: %@", styleURL); - // An access token is required to load any default style, including Streets. - if (![MGLAccountManager accessToken] && [styleURL.scheme isEqualToString:@"mapbox"]) { - NSLog(@"Cannot set the style URL to %@ because no access token has been specified.", styleURL); - return; - } - - styleURL = styleURL.mgl_URLByStandardizingScheme; - self.style = nil; - _mbglMap->getStyle().loadURL(styleURL.absoluteString.UTF8String); -} - -- (IBAction)reloadStyle:(__unused id)sender { - MGLLogInfo(@"Reloading style."); - NSURL *styleURL = self.styleURL; - _mbglMap->getStyle().loadURL(""); - self.styleURL = styleURL; -} - -- (void)setPrefetchesTiles:(BOOL)prefetchesTiles -{ - _mbglMap->setPrefetchZoomDelta(prefetchesTiles ? mbgl::util::DEFAULT_PREFETCH_ZOOM_DELTA : 0); -} - -- (mbgl::Map *)mbglMap { - return _mbglMap; -} - -- (mbgl::Renderer *)renderer { - return _rendererFrontend->getRenderer(); -} - -#pragma mark View hierarchy and drawing - -- (void)viewWillMoveToWindow:(NSWindow *)newWindow { - [self deselectAnnotation:self.selectedAnnotation]; - if (!self.dormant && !newWindow) { - self.dormant = YES; - } - - [self.window removeObserver:self forKeyPath:@"contentLayoutRect"]; - [self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"]; -} - -- (void)viewDidMoveToWindow { - NSWindow *window = self.window; - if (self.dormant && window) { - self.dormant = NO; - } - - if (window && _mbglMap->getMapOptions().constrainMode() == mbgl::ConstrainMode::None) { - _mbglMap->setConstrainMode(mbgl::ConstrainMode::HeightOnly); - } - - [window addObserver:self - forKeyPath:@"contentLayoutRect" - options:NSKeyValueObservingOptionInitial - context:NULL]; - [window addObserver:self - forKeyPath:@"titlebarAppearsTransparent" - options:NSKeyValueObservingOptionInitial - context:NULL]; -} - -- (BOOL)wantsLayer { - return YES; -} - -- (BOOL)wantsBestResolutionOpenGLSurface { - // Use an OpenGL layer, except when drawing the designable, which is just - // ordinary Cocoa. - return !_isTargetingInterfaceBuilder; -} - -- (CGLContextObj)context { - return _mbglView->getCGLContextObj(); -} - -- (void)setFrame:(NSRect)frame { - super.frame = frame; - if (!_isTargetingInterfaceBuilder) { - _mbglMap->setSize(self.size); - } -} - -- (void)updateConstraints { - // Place the zoom controls at the lower-right corner of the view. - [self addConstraint: - [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:_zoomControls - attribute:NSLayoutAttributeBottom - multiplier:1 - constant:MGLOrnamentPadding]]; - [self addConstraint: - [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:_zoomControls - attribute:NSLayoutAttributeTrailing - multiplier:1 - constant:MGLOrnamentPadding]]; - - // Center the compass above the zoom controls, assuming that the compass is - // narrower than the zoom controls. - [self addConstraint: - [NSLayoutConstraint constraintWithItem:_compass - attribute:NSLayoutAttributeCenterX - relatedBy:NSLayoutRelationEqual - toItem:_zoomControls - attribute:NSLayoutAttributeCenterX - multiplier:1 - constant:0]]; - [self addConstraint: - [NSLayoutConstraint constraintWithItem:_zoomControls - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:_compass - attribute:NSLayoutAttributeBottom - multiplier:1 - constant:8]]; - - // Place the logo view in the lower-left corner of the view, accounting for - // the logo’s alignment rect. - [self addConstraint: - [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:_logoView - attribute:NSLayoutAttributeBottom - multiplier:1 - constant:MGLOrnamentPadding - _logoView.image.alignmentRect.origin.y]]; - [self addConstraint: - [NSLayoutConstraint constraintWithItem:_logoView - attribute:NSLayoutAttributeLeading - relatedBy:NSLayoutRelationEqual - toItem:self - attribute:NSLayoutAttributeLeading - multiplier:1 - constant:MGLOrnamentPadding - _logoView.image.alignmentRect.origin.x]]; - - // Place the attribution view to the right of the logo view and size it to - // fit the buttons inside. - [self addConstraint:[NSLayoutConstraint constraintWithItem:_logoView - attribute:NSLayoutAttributeBaseline - relatedBy:NSLayoutRelationEqual - toItem:_attributionView - attribute:NSLayoutAttributeBaseline - multiplier:1 - constant:_logoView.image.alignmentRect.origin.y]]; - [self addConstraint:[NSLayoutConstraint constraintWithItem:_attributionView - attribute:NSLayoutAttributeLeading - relatedBy:NSLayoutRelationEqual - toItem:_logoView - attribute:NSLayoutAttributeTrailing - multiplier:1 - constant:8]]; - - [super updateConstraints]; -} - -- (void)renderSync { - if (!self.dormant && _rendererFrontend) { - _rendererFrontend->render(); - - if (_isPrinting) { - _isPrinting = NO; - NSImage *image = [[NSImage alloc] initWithMGLPremultipliedImage:_mbglView->readStillImage()]; - [self performSelector:@selector(printWithImage:) withObject:image afterDelay:0]; - } - -// [self updateUserLocationAnnotationView]; - } -} - -- (BOOL)isTargetingInterfaceBuilder { - return _isTargetingInterfaceBuilder; -} - -- (void)setNeedsRerender { - MGLAssertIsMainThread(); - - [self.layer setNeedsDisplay]; -} - -- (void)cameraWillChangeAnimated:(BOOL)animated { - if (!_mbglMap) { - return; - } - - if ([self.delegate respondsToSelector:@selector(mapView:cameraWillChangeAnimated:)]) { - [self.delegate mapView:self cameraWillChangeAnimated:animated]; - } -} - -- (void)cameraIsChanging { - if (!_mbglMap) { - return; - } - - // Update a minimum of UI that needs to stay attached to the map - // while animating. - [self updateCompass]; - [self updateAnnotationCallouts]; - - if ([self.delegate respondsToSelector:@selector(mapViewCameraIsChanging:)]) { - [self.delegate mapViewCameraIsChanging:self]; - } -} - -- (void)cameraDidChangeAnimated:(BOOL)animated { - if (!_mbglMap) { - return; - } - - // Update all UI at the end of an animation or atomic change to the - // viewport. More expensive updates can happen here, but care should - // still be taken to minimize the work done here because scroll - // gesture recognition and momentum scrolling is performed as a - // series of atomic changes, not an animation. - [self updateZoomControls]; - [self updateCompass]; - [self updateAnnotationCallouts]; - [self updateAnnotationTrackingAreas]; - - if ([self.delegate respondsToSelector:@selector(mapView:cameraDidChangeAnimated:)]) { - [self.delegate mapView:self cameraDidChangeAnimated:animated]; - } -} - -- (void)mapViewWillStartLoadingMap { - if (!_mbglMap) { - return; - } - - if ([self.delegate respondsToSelector:@selector(mapViewWillStartLoadingMap:)]) { - [self.delegate mapViewWillStartLoadingMap:self]; - } -} - -- (void)mapViewDidFinishLoadingMap { - if (!_mbglMap) { - return; - } - - [self.style willChangeValueForKey:@"sources"]; - [self.style didChangeValueForKey:@"sources"]; - [self.style willChangeValueForKey:@"layers"]; - [self.style didChangeValueForKey:@"layers"]; - if ([self.delegate respondsToSelector:@selector(mapViewDidFinishLoadingMap:)]) { - [self.delegate mapViewDidFinishLoadingMap:self]; - } -} - -- (void)mapViewDidFailLoadingMapWithError:(NSError *)error { - if (!_mbglMap) { - return; - } - - if ([self.delegate respondsToSelector:@selector(mapViewDidFailLoadingMap:withError:)]) { - [self.delegate mapViewDidFailLoadingMap:self withError:error]; - } -} - -- (void)mapViewWillStartRenderingFrame { - if (!_mbglMap) { - return; - } - - if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingFrame:)]) { - [self.delegate mapViewWillStartRenderingFrame:self]; - } -} - -- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered { - if (!_mbglMap) { - return; - } - - if (_isChangingAnnotationLayers) { - _isChangingAnnotationLayers = NO; - [self.style didChangeValueForKey:@"layers"]; - } - if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)]) { - [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered]; - } -} - -- (void)mapViewWillStartRenderingMap { - if (!_mbglMap) { - return; - } - - if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingMap:)]) { - [self.delegate mapViewWillStartRenderingMap:self]; - } -} - -- (void)mapViewDidFinishRenderingMapFullyRendered:(BOOL)fullyRendered { - if (!_mbglMap) { - return; - } - - if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)]) { - [self.delegate mapViewDidFinishRenderingMap:self fullyRendered:fullyRendered]; - } -} - -- (void)mapViewDidBecomeIdle { - if (!_mbglMap) { - return; - } - - if ([self.delegate respondsToSelector:@selector(mapViewDidBecomeIdle:)]) { - [self.delegate mapViewDidBecomeIdle:self]; - } -} - -- (void)mapViewDidFinishLoadingStyle { - if (!_mbglMap) { - return; - } - - self.style = [[MGLStyle alloc] initWithRawStyle:&_mbglMap->getStyle() mapView:self]; - if ([self.delegate respondsToSelector:@selector(mapView:didFinishLoadingStyle:)]) - { - [self.delegate mapView:self didFinishLoadingStyle:self.style]; - } -} - -- (void)sourceDidChange:(MGLSource *)source { - if (!_mbglMap) { - return; - } - // Attribution only applies to tiled sources - if ([source isKindOfClass:[MGLTileSource class]]) { - [self installAttributionView]; - } - self.needsUpdateConstraints = YES; - self.needsDisplay = YES; -} - -- (BOOL)shouldRemoveStyleImage:(NSString *)imageName { - if ([self.delegate respondsToSelector:@selector(mapView:shouldRemoveStyleImage:)]) { - return [self.delegate mapView:self shouldRemoveStyleImage:imageName]; - } - - return YES; -} - -#pragma mark Printing - -- (void)print:(__unused id)sender { - _isPrinting = YES; - [self setNeedsRerender]; -} - -- (void)printWithImage:(NSImage *)image { - NSImageView *imageView = [[NSImageView alloc] initWithFrame:self.bounds]; - imageView.image = image; - - NSPrintOperation *op = [NSPrintOperation printOperationWithView:imageView]; - [op runOperation]; -} - -#pragma mark Viewport - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingCenterCoordinate { - return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil]; -} - -- (CLLocationCoordinate2D)centerCoordinate { - mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); - return MGLLocationCoordinate2DFromLatLng(*_mbglMap->getCameraOptions(padding).center); -} - -- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate { - MGLLogDebug(@"Setting centerCoordinate: %@", MGLStringFromCLLocationCoordinate2D(centerCoordinate)); - [self setCenterCoordinate:centerCoordinate animated:NO]; -} - -- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate animated:(BOOL)animated { - [self setCenterCoordinate:centerCoordinate animated:animated completionHandler:nil]; -} - -- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion { - MGLLogDebug(@"Setting centerCoordinate: %@ animated: %@", MGLStringFromCLLocationCoordinate2D(centerCoordinate), MGLStringFromBOOL(animated)); - mbgl::AnimationOptions animationOptions = MGLDurationFromTimeInterval(animated ? MGLAnimationDuration : 0); - animationOptions.transitionFinishFn = ^() { - [self didChangeValueForKey:@"centerCoordinate"]; - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(); - }); - } - }; - - [self willChangeValueForKey:@"centerCoordinate"]; - _mbglMap->easeTo(mbgl::CameraOptions() - .withCenter(MGLLatLngFromLocationCoordinate2D(centerCoordinate)) - .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets)), - animationOptions); -} - -- (void)offsetCenterCoordinateBy:(NSPoint)delta animated:(BOOL)animated { - [self willChangeValueForKey:@"centerCoordinate"]; - _mbglMap->cancelTransitions(); - MGLMapCamera *oldCamera = self.camera; - _mbglMap->moveBy({ delta.x, delta.y }, - MGLDurationFromTimeInterval(animated ? MGLAnimationDuration : 0)); - if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { - self.camera = oldCamera; - } - [self didChangeValueForKey:@"centerCoordinate"]; -} - -- (CLLocationDegrees)pendingLatitude { - return _pendingLatitude; -} - -- (void)setPendingLatitude:(CLLocationDegrees)pendingLatitude { - _pendingLatitude = pendingLatitude; -} - -- (CLLocationDegrees)pendingLongitude { - return _pendingLongitude; -} - -- (void)setPendingLongitude:(CLLocationDegrees)pendingLongitude { - _pendingLongitude = pendingLongitude; -} - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingZoomLevel { - return [NSSet setWithObject:@"camera"]; -} - -- (double)zoomLevel { - mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); - return *_mbglMap->getCameraOptions(padding).zoom; -} - -- (void)setZoomLevel:(double)zoomLevel { - MGLLogDebug(@"Setting zoomLevel: %f", zoomLevel); - [self setZoomLevel:zoomLevel animated:NO]; -} - -- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated { - MGLLogDebug(@"Setting zoomLevel: %f animated: %@", zoomLevel, MGLStringFromBOOL(animated)); - [self willChangeValueForKey:@"zoomLevel"]; - _mbglMap->easeTo(mbgl::CameraOptions() - .withZoom(zoomLevel) - .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets)), - MGLDurationFromTimeInterval(animated ? MGLAnimationDuration : 0)); - [self didChangeValueForKey:@"zoomLevel"]; -} - -- (void)setZoomLevel:(double)zoomLevel atPoint:(NSPoint)point animated:(BOOL)animated { - [self willChangeValueForKey:@"centerCoordinate"]; - [self willChangeValueForKey:@"zoomLevel"]; - MGLMapCamera *oldCamera = self.camera; - mbgl::ScreenCoordinate center(point.x, self.bounds.size.height - point.y); - _mbglMap->easeTo(mbgl::CameraOptions() - .withZoom(zoomLevel) - .withAnchor(center), - MGLDurationFromTimeInterval(animated ? MGLAnimationDuration : 0)); - if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { - self.camera = oldCamera; - } - [self didChangeValueForKey:@"zoomLevel"]; - [self didChangeValueForKey:@"centerCoordinate"]; -} - -- (void)setMinimumZoomLevel:(double)minimumZoomLevel -{ - MGLLogDebug(@"Setting minimumZoomLevel: %f", minimumZoomLevel); - _mbglMap->setBounds(mbgl::BoundOptions().withMinZoom(minimumZoomLevel)); -} - -- (void)setMaximumZoomLevel:(double)maximumZoomLevel -{ - MGLLogDebug(@"Setting maximumZoomLevel: %f", maximumZoomLevel); - _mbglMap->setBounds(mbgl::BoundOptions().withMaxZoom(maximumZoomLevel)); -} - -- (double)maximumZoomLevel { - return *_mbglMap->getBounds().maxZoom; -} - -- (double)minimumZoomLevel { - return *_mbglMap->getBounds().minZoom; -} - -/// Respond to a click on the zoom control. -- (IBAction)zoomInOrOut:(NSSegmentedControl *)sender { - switch (sender.selectedSegment) { - case 0: - // Zoom out. - [self moveToEndOfParagraph:sender]; - break; - case 1: - // Zoom in. - [self moveToBeginningOfParagraph:sender]; - break; - default: - break; - } -} - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingDirection { - return [NSSet setWithObject:@"camera"]; -} - -- (CLLocationDirection)direction { - return mbgl::util::wrap(*_mbglMap->getCameraOptions().bearing, 0., 360.); -} - -- (void)setDirection:(CLLocationDirection)direction { - MGLLogDebug(@"Setting direction: %f", direction); - [self setDirection:direction animated:NO]; -} - -- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated { - MGLLogDebug(@"Setting direction: %f animated: %@", direction, MGLStringFromBOOL(animated)); - [self willChangeValueForKey:@"direction"]; - _mbglMap->easeTo(mbgl::CameraOptions() - .withBearing(direction) - .withPadding(MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets)), - MGLDurationFromTimeInterval(animated ? MGLAnimationDuration : 0)); - [self didChangeValueForKey:@"direction"]; -} - -- (void)offsetDirectionBy:(CLLocationDegrees)delta animated:(BOOL)animated { - [self setDirection:*_mbglMap->getCameraOptions().bearing + delta animated:animated]; -} - -+ (NSSet<NSString *> *)keyPathsForValuesAffectingCamera { - return [NSSet setWithObjects:@"latitude", @"longitude", @"centerCoordinate", @"zoomLevel", @"direction", nil]; -} - -- (MGLMapCamera *)camera { - mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); - return [self cameraForCameraOptions:_mbglMap->getCameraOptions(padding)]; -} - -- (void)setCamera:(MGLMapCamera *)camera { - MGLLogDebug(@"Setting camera: %@", camera); - [self setCamera:camera animated:NO]; -} - -- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated { - MGLLogDebug(@"Setting camera: %@ animated: %@", camera, MGLStringFromBOOL(animated)); - [self setCamera:camera withDuration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:NULL]; -} - -- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion { - MGLLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@", camera, duration, function); - [self setCamera:camera withDuration:duration animationTimingFunction:function edgePadding:NSEdgeInsetsZero completionHandler:completion]; -} - -- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(NSEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion { - edgePadding = MGLEdgeInsetsInsetEdgeInset(edgePadding, self.contentInsets); - mbgl::AnimationOptions animationOptions; - if (duration > 0) { - animationOptions.duration.emplace(MGLDurationFromTimeInterval(duration)); - animationOptions.easing.emplace(MGLUnitBezierForMediaTimingFunction(function)); - } - if (completion) { - animationOptions.transitionFinishFn = [completion]() { - // Must run asynchronously after the transition is completely over. - // Otherwise, a call to -setCamera: within the completion handler - // would reenter the completion handler’s caller. - dispatch_async(dispatch_get_main_queue(), ^{ - completion(); - }); - }; - } - - if ([self.camera isEqualToMapCamera:camera]) { - if (completion) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - completion(); - }); - } - return; - } - - [self willChangeValueForKey:@"camera"]; - _mbglMap->cancelTransitions(); - mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding]; - _mbglMap->easeTo(cameraOptions, animationOptions); - [self didChangeValueForKey:@"camera"]; -} - -- (void)flyToCamera:(MGLMapCamera *)camera completionHandler:(nullable void (^)(void))completion { - MGLLogDebug(@"Setting flyToCamera: %@ completionHandler: %@", camera, completion); - [self flyToCamera:camera withDuration:-1 completionHandler:completion]; -} - -- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration completionHandler:(nullable void (^)(void))completion { - MGLLogDebug(@"Setting flyToCamera: %@ withDuration: %f completionHandler: %@", camera, duration, completion); - [self flyToCamera:camera withDuration:duration peakAltitude:-1 completionHandler:completion]; -} - -- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion { - MGLLogDebug(@"Setting flyToCamera: %@ withDuration: %f peakAltitude: %f completionHandler: %@", camera, duration, peakAltitude, completion); - mbgl::AnimationOptions animationOptions; - if (duration >= 0) { - animationOptions.duration = MGLDurationFromTimeInterval(duration); - } - if (peakAltitude >= 0) { - CLLocationDegrees peakLatitude = (self.centerCoordinate.latitude + camera.centerCoordinate.latitude) / 2; - CLLocationDegrees peakPitch = (self.camera.pitch + camera.pitch) / 2; - animationOptions.minZoom = MGLZoomLevelForAltitude(peakAltitude, peakPitch, - peakLatitude, self.frame.size); - } - if (completion) { - animationOptions.transitionFinishFn = [completion]() { - // Must run asynchronously after the transition is completely over. - // Otherwise, a call to -setCamera: within the completion handler - // would reenter the completion handler’s caller. - dispatch_async(dispatch_get_main_queue(), ^{ - completion(); - }); - }; - } - - if ([self.camera isEqualToMapCamera:camera]) { - if (completion) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - completion(); - }); - } - return; - } - - [self willChangeValueForKey:@"camera"]; - _mbglMap->cancelTransitions(); - mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:self.contentInsets]; - _mbglMap->flyTo(cameraOptions, animationOptions); - [self didChangeValueForKey:@"camera"]; -} - -/// Returns a CameraOptions object that specifies parameters for animating to -/// the given camera. -- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MGLMapCamera *)camera edgePadding:(NSEdgeInsets) edgePadding { - mbgl::CameraOptions options; - options.center = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate); - options.padding = MGLEdgeInsetsFromNSEdgeInsets(edgePadding); - options.zoom = MGLZoomLevelForAltitude(camera.altitude, camera.pitch, - camera.centerCoordinate.latitude, - self.frame.size); - if (camera.heading >= 0) { - options.bearing = camera.heading; - } - if (camera.pitch >= 0) { - options.pitch = camera.pitch; - } - return options; -} - -+ (NSSet *)keyPathsForValuesAffectingVisibleCoordinateBounds { - return [NSSet setWithObjects:@"centerCoordinate", @"zoomLevel", @"direction", @"bounds", nil]; -} - -- (MGLCoordinateBounds)visibleCoordinateBounds { - return [self convertRect:self.bounds toCoordinateBoundsFromView:self]; -} - -- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds { - MGLLogDebug(@"Setting visibleCoordinateBounds: %@", MGLStringFromCoordinateBounds(bounds)); - [self setVisibleCoordinateBounds:bounds animated:NO]; -} - -- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds animated:(BOOL)animated { - MGLLogDebug(@"Setting visibleCoordinateBounds: %@ animated: %@", MGLStringFromCoordinateBounds(bounds), MGLStringFromBOOL(animated)); - [self setVisibleCoordinateBounds:bounds edgePadding:NSEdgeInsetsZero animated:animated]; -} - -- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated { - [self setVisibleCoordinateBounds:bounds edgePadding:insets animated:animated completionHandler:nil]; -} - -- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion { - _mbglMap->cancelTransitions(); - - mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets); - padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); - mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding); - mbgl::AnimationOptions animationOptions; - if (animated) { - animationOptions.duration = MGLDurationFromTimeInterval(MGLAnimationDuration); - } - - MGLMapCamera *camera = [self cameraForCameraOptions:cameraOptions]; - if ([self.camera isEqualToMapCamera:camera]) { - completion(); - return; - } - - [self willChangeValueForKey:@"visibleCoordinateBounds"]; - animationOptions.transitionFinishFn = ^() { - [self didChangeValueForKey:@"visibleCoordinateBounds"]; - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(); - }); - } - }; - _mbglMap->easeTo(cameraOptions, animationOptions); -} - -- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds { - return [self cameraThatFitsCoordinateBounds:bounds edgePadding:NSEdgeInsetsZero]; -} - -- (MGLMapCamera *)cameraThatFitsCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets { - mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets); - padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); - mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding); - return [self cameraForCameraOptions:cameraOptions]; -} - -- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets -{ - mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets); - padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); - - MGLMapCamera *currentCamera = self.camera; - CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch; - CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading; - - mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding, direction, pitch); - return [self cameraForCameraOptions:cameraOptions]; -} - -- (MGLMapCamera *)camera:(MGLMapCamera *)camera fittingShape:(MGLShape *)shape edgePadding:(NSEdgeInsets)insets { - mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets); - padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); - - MGLMapCamera *currentCamera = self.camera; - CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch; - CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading; - - mbgl::CameraOptions cameraOptions = _mbglMap->cameraForGeometry([shape geometryObject], padding, direction, pitch); - - return [self cameraForCameraOptions: cameraOptions]; -} - -- (MGLMapCamera *)cameraThatFitsShape:(MGLShape *)shape direction:(CLLocationDirection)direction edgePadding:(NSEdgeInsets)insets { - mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets); - padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); - - mbgl::CameraOptions cameraOptions = _mbglMap->cameraForGeometry([shape geometryObject], padding, direction); - - return [self cameraForCameraOptions:cameraOptions]; -} - -- (MGLMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions { - mbgl::CameraOptions mapCamera = _mbglMap->getCameraOptions(); - CLLocationCoordinate2D centerCoordinate = MGLLocationCoordinate2DFromLatLng(cameraOptions.center ? *cameraOptions.center : *mapCamera.center); - double zoomLevel = cameraOptions.zoom ? *cameraOptions.zoom : self.zoomLevel; - CLLocationDirection direction = cameraOptions.bearing ? mbgl::util::wrap(*cameraOptions.bearing, 0., 360.) : self.direction; - CGFloat pitch = cameraOptions.pitch ? *cameraOptions.pitch : *mapCamera.pitch; - CLLocationDistance altitude = MGLAltitudeForZoomLevel(zoomLevel, pitch, - centerCoordinate.latitude, - self.frame.size); - return [MGLMapCamera cameraLookingAtCenterCoordinate:centerCoordinate - altitude:altitude - pitch:pitch - heading:direction]; -} - -- (void)setAutomaticallyAdjustsContentInsets:(BOOL)automaticallyAdjustsContentInsets { - _automaticallyAdjustsContentInsets = automaticallyAdjustsContentInsets; - [self adjustContentInsets]; -} - -/// Updates `contentInsets` to reflect the current window geometry. -- (void)adjustContentInsets { - if (!_automaticallyAdjustsContentInsets) { - return; - } - - NSEdgeInsets contentInsets = self.contentInsets; - if ((self.window.styleMask & NSFullSizeContentViewWindowMask) - && !self.window.titlebarAppearsTransparent) { - NSRect contentLayoutRect = [self convertRect:self.window.contentLayoutRect fromView:nil]; - if (NSMaxX(contentLayoutRect) > 0 && NSMaxY(contentLayoutRect) > 0) { - contentInsets = NSEdgeInsetsMake(NSHeight(self.bounds) - NSMaxY(contentLayoutRect), - MAX(NSMinX(contentLayoutRect), 0), - MAX(NSMinY(contentLayoutRect), 0), - NSWidth(self.bounds) - NSMaxX(contentLayoutRect)); - } - } else { - contentInsets = NSEdgeInsetsZero; - } - - self.contentInsets = contentInsets; -} - -- (void)setContentInsets:(NSEdgeInsets)contentInsets { - [self setContentInsets:contentInsets animated:NO completionHandler:nil]; -} - -- (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated { - [self setContentInsets:contentInsets animated:animated completionHandler:nil]; -} - -- (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion { - if (NSEdgeInsetsEqual(contentInsets, self.contentInsets)) { - if (completion) { - completion(); - } - return; - } - MGLLogDebug(@"Setting contentInset: %@ animated:", MGLStringFromNSEdgeInsets(contentInsets), MGLStringFromBOOL(animated)); - // After adjusting the content insets, move the center coordinate from the - // old frame of reference to the new one represented by the newly set - // content insets. - CLLocationCoordinate2D oldCenter = self.centerCoordinate; - _contentInsets = contentInsets; - [self setCenterCoordinate:oldCenter animated:animated completionHandler:completion]; -} - -#pragma mark Mouse events and gestures - -- (BOOL)acceptsFirstResponder { - return YES; -} - -/// Drag to pan, plus drag to zoom, rotate, and tilt when a modifier key is held -/// down. -- (void)handlePanGesture:(NSPanGestureRecognizer *)gestureRecognizer { - NSPoint delta = [gestureRecognizer translationInView:self]; - NSPoint endPoint = [gestureRecognizer locationInView:self]; - NSPoint startPoint = NSMakePoint(endPoint.x - delta.x, endPoint.y - delta.y); - - NSEventModifierFlags flags = [NSApp currentEvent].modifierFlags; - if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { - [self.window invalidateCursorRectsForView:self]; - _mbglMap->setGestureInProgress(true); - - if (![self isPanningWithGesture]) { - // Hide the cursor except when panning. - CGDisplayHideCursor(kCGDirectMainDisplay); - _didHideCursorDuringGesture = YES; - } - } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded - || gestureRecognizer.state == NSGestureRecognizerStateCancelled) { - _mbglMap->setGestureInProgress(false); - [self.window invalidateCursorRectsForView:self]; - - if (_didHideCursorDuringGesture) { - _didHideCursorDuringGesture = NO; - // Move the cursor back to the start point and show it again, creating - // the illusion that it has stayed in place during the entire gesture. - CGPoint cursorPoint = [self convertPoint:startPoint toView:nil]; - cursorPoint = [self.window convertRectToScreen:{ cursorPoint, NSZeroSize }].origin; - cursorPoint.y = self.window.screen.frame.size.height - cursorPoint.y; - CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cursorPoint); - CGDisplayShowCursor(kCGDirectMainDisplay); - } - } - - if (flags & NSShiftKeyMask) { - // Shift-drag to zoom. - if (!self.zoomEnabled) { - return; - } - - _mbglMap->cancelTransitions(); - - if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { - _zoomAtBeginningOfGesture = [self zoomLevel]; - } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { - CGFloat newZoomLevel = _zoomAtBeginningOfGesture - delta.y / 75; - [self setZoomLevel:newZoomLevel atPoint:startPoint animated:NO]; - } - } else if (flags & NSAlternateKeyMask) { - // Option-drag to rotate and/or tilt. - _mbglMap->cancelTransitions(); - - if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { - _directionAtBeginningOfGesture = self.direction; - _pitchAtBeginningOfGesture = *_mbglMap->getCameraOptions().pitch; - } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { - MGLMapCamera *oldCamera = self.camera; - BOOL didChangeCamera = NO; - mbgl::ScreenCoordinate center(startPoint.x, self.bounds.size.height - startPoint.y); - if (self.rotateEnabled) { - CLLocationDirection newDirection = _directionAtBeginningOfGesture - delta.x / 10; - [self willChangeValueForKey:@"direction"]; - _mbglMap->jumpTo(mbgl::CameraOptions().withBearing(newDirection).withAnchor(center)); - didChangeCamera = YES; - [self didChangeValueForKey:@"direction"]; - } - if (self.pitchEnabled) { - _mbglMap->jumpTo(mbgl::CameraOptions().withPitch(_pitchAtBeginningOfGesture + delta.y / 5).withAnchor(center)); - didChangeCamera = YES; - } - - if (didChangeCamera - && [self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { - self.camera = oldCamera; - } - } - } else if (self.scrollEnabled) { - // Otherwise, drag to pan. - _mbglMap->cancelTransitions(); - - if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { - delta.y *= -1; - [self offsetCenterCoordinateBy:delta animated:NO]; - [gestureRecognizer setTranslation:NSZeroPoint inView:nil]; - } - } -} - -/// Returns whether the user is panning using a gesture. -- (BOOL)isPanningWithGesture { - NSGestureRecognizerState state = _panGestureRecognizer.state; - NSEventModifierFlags flags = [NSApp currentEvent].modifierFlags; - return ((state == NSGestureRecognizerStateBegan || state == NSGestureRecognizerStateChanged) - && !(flags & NSShiftKeyMask || flags & NSAlternateKeyMask)); -} - -/// Pinch to zoom. -- (void)handleMagnificationGesture:(NSMagnificationGestureRecognizer *)gestureRecognizer { - if (!self.zoomEnabled) { - return; - } - - _mbglMap->cancelTransitions(); - - if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { - _mbglMap->setGestureInProgress(true); - _zoomAtBeginningOfGesture = [self zoomLevel]; - } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { - NSPoint zoomInPoint = [gestureRecognizer locationInView:self]; - mbgl::ScreenCoordinate center(zoomInPoint.x, self.bounds.size.height - zoomInPoint.y); - if (gestureRecognizer.magnification > -1) { - [self willChangeValueForKey:@"zoomLevel"]; - [self willChangeValueForKey:@"centerCoordinate"]; - MGLMapCamera *oldCamera = self.camera; - _mbglMap->jumpTo(mbgl::CameraOptions() - .withZoom(_zoomAtBeginningOfGesture + log2(1 + gestureRecognizer.magnification)) - .withAnchor(center)); - if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { - self.camera = oldCamera; - } - [self didChangeValueForKey:@"centerCoordinate"]; - [self didChangeValueForKey:@"zoomLevel"]; - } - } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded - || gestureRecognizer.state == NSGestureRecognizerStateCancelled) { - _mbglMap->setGestureInProgress(false); - } -} - -/// Click or tap to select an annotation. -- (void)handleClickGesture:(NSClickGestureRecognizer *)gestureRecognizer { - if (gestureRecognizer.state != NSGestureRecognizerStateEnded - || [self subviewContainingGesture:gestureRecognizer]) { - return; - } - - NSPoint gesturePoint = [gestureRecognizer locationInView:self]; - MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:gesturePoint persistingResults:YES]; - if (hitAnnotationTag != MGLAnnotationTagNotFound) { - if (hitAnnotationTag != _selectedAnnotationTag) { - id <MGLAnnotation> annotation = [self annotationWithTag:hitAnnotationTag]; - NSAssert(annotation, @"Cannot select nonexistent annotation with tag %llu", hitAnnotationTag); - [self selectAnnotation:annotation atPoint:gesturePoint]; - } - } else { - [self deselectAnnotation:self.selectedAnnotation]; - } -} - -/// Right-click to show the context menu. -- (void)handleRightClickGesture:(NSClickGestureRecognizer *)gestureRecognizer { - NSMenu *menu = [self menuForEvent:[NSApp currentEvent]]; - if (menu) { - [NSMenu popUpContextMenu:menu withEvent:[NSApp currentEvent] forView:self]; - } -} - -/// Double-click or double-tap to zoom in. -- (void)handleDoubleClickGesture:(NSClickGestureRecognizer *)gestureRecognizer { - if (!self.zoomEnabled || gestureRecognizer.state != NSGestureRecognizerStateEnded - || [self subviewContainingGesture:gestureRecognizer]) { - return; - } - - _mbglMap->cancelTransitions(); - - NSPoint gesturePoint = [gestureRecognizer locationInView:self]; - [self setZoomLevel:round(self.zoomLevel) + 1 atPoint:gesturePoint animated:YES]; -} - -- (void)smartMagnifyWithEvent:(NSEvent *)event { - if (!self.zoomEnabled) { - return; - } - - _mbglMap->cancelTransitions(); - - // Tap with two fingers (“right-click”) to zoom out on mice but not trackpads. - NSPoint gesturePoint = [self convertPoint:event.locationInWindow fromView:nil]; - [self setZoomLevel:round(self.zoomLevel) - 1 atPoint:gesturePoint animated:YES]; -} - -/// Rotate fingers to rotate. -- (void)handleRotationGesture:(NSRotationGestureRecognizer *)gestureRecognizer { - if (!self.rotateEnabled) { - return; - } - - _mbglMap->cancelTransitions(); - - if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { - _mbglMap->setGestureInProgress(true); - _directionAtBeginningOfGesture = self.direction; - } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { - MGLMapCamera *oldCamera = self.camera; - - NSPoint rotationPoint = [gestureRecognizer locationInView:self]; - mbgl::ScreenCoordinate anchor(rotationPoint.x, self.bounds.size.height - rotationPoint.y); - _mbglMap->jumpTo(mbgl::CameraOptions() - .withBearing(_directionAtBeginningOfGesture + gestureRecognizer.rotationInDegrees) - .withAnchor(anchor)); - - if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { - self.camera = oldCamera; - } - } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded - || gestureRecognizer.state == NSGestureRecognizerStateCancelled) { - _mbglMap->setGestureInProgress(false); - } -} - -- (BOOL)wantsScrollEventsForSwipeTrackingOnAxis:(__unused NSEventGestureAxis)axis { - // Track both horizontal and vertical swipes in -scrollWheel:. - return YES; -} - -- (void)scrollWheel:(NSEvent *)event { - // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#10_7Dragging - BOOL isScrollWheel = event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone && !event.hasPreciseScrollingDeltas; - if (isScrollWheel || [[NSUserDefaults standardUserDefaults] boolForKey:MGLScrollWheelZoomsMapViewDefaultKey]) { - // A traditional, vertical scroll wheel zooms instead of panning. - if (self.zoomEnabled) { - const double delta = - event.scrollingDeltaY / ([event hasPreciseScrollingDeltas] ? 100 : 10); - if (delta != 0) { - double scale = 2.0 / (1.0 + std::exp(-std::abs(delta))); - - // Zooming out. - if (delta < 0) { - scale = 1.0 / scale; - } - - NSPoint gesturePoint = [self convertPoint:event.locationInWindow fromView:nil]; - [self setZoomLevel:self.zoomLevel + log2(scale) atPoint:gesturePoint animated:NO]; - } - } - } else if (self.scrollEnabled - && _magnificationGestureRecognizer.state == NSGestureRecognizerStatePossible - && _rotationGestureRecognizer.state == NSGestureRecognizerStatePossible) { - // Scroll to pan. - _mbglMap->cancelTransitions(); - - CGFloat x = event.scrollingDeltaX; - CGFloat y = event.scrollingDeltaY; - if (x || y) { - [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO]; - } - - // Drift pan. - if (event.momentumPhase != NSEventPhaseNone) { - [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO]; - } - } -} - -/// Returns the subview that the gesture is located in. -- (NSView *)subviewContainingGesture:(NSGestureRecognizer *)gestureRecognizer { - if (NSPointInRect([gestureRecognizer locationInView:self.compass], self.compass.bounds)) { - return self.compass; - } - if (NSPointInRect([gestureRecognizer locationInView:self.zoomControls], self.zoomControls.bounds)) { - return self.zoomControls; - } - if (NSPointInRect([gestureRecognizer locationInView:self.attributionView], self.attributionView.bounds)) { - return self.attributionView; - } - return nil; -} - -#pragma mark NSGestureRecognizerDelegate methods -- (BOOL)gestureRecognizer:(NSGestureRecognizer *)gestureRecognizer shouldAttemptToRecognizeWithEvent:(NSEvent *)event { - if (gestureRecognizer == _singleClickRecognizer) { - if (!self.selectedAnnotation) { - NSPoint gesturePoint = [self convertPoint:[event locationInWindow] fromView:nil]; - MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:gesturePoint persistingResults:NO]; - if (hitAnnotationTag == MGLAnnotationTagNotFound) { - return NO; - } - } - } - return YES; -} - -#pragma mark Keyboard events - -- (void)keyDown:(NSEvent *)event { - // This is the recommended way to handle arrow key presses, causing - // methods like -moveUp: and -moveToBeginningOfParagraph: to be called - // for various standard keybindings. - [self interpretKeyEvents:@[event]]; -} - -// The following action methods are declared in NSResponder.h. - -- (void)insertTab:(id)sender { - if (self.window.firstResponder == self) { - [self.window selectNextKeyView:self]; - } -} - -- (void)insertBacktab:(id)sender { - if (self.window.firstResponder == self) { - [self.window selectPreviousKeyView:self]; - } -} - -- (void)insertText:(NSString *)insertString { - switch (insertString.length == 1 ? [insertString characterAtIndex:0] : 0) { - case '-': - [self moveToEndOfParagraph:nil]; - break; - - case '+': - case '=': - [self moveToBeginningOfParagraph:nil]; - break; - - default: - [super insertText:insertString]; - break; - } -} - -- (IBAction)moveUp:(__unused id)sender { - [self offsetCenterCoordinateBy:NSMakePoint(0, MGLKeyPanningIncrement) animated:YES]; -} - -- (IBAction)moveDown:(__unused id)sender { - [self offsetCenterCoordinateBy:NSMakePoint(0, -MGLKeyPanningIncrement) animated:YES]; -} - -- (IBAction)moveLeft:(__unused id)sender { - [self offsetCenterCoordinateBy:NSMakePoint(MGLKeyPanningIncrement, 0) animated:YES]; -} - -- (IBAction)moveRight:(__unused id)sender { - [self offsetCenterCoordinateBy:NSMakePoint(-MGLKeyPanningIncrement, 0) animated:YES]; -} - -- (IBAction)moveToBeginningOfParagraph:(__unused id)sender { - if (self.zoomEnabled) { - [self setZoomLevel:round(self.zoomLevel) + 1 animated:YES]; - } -} - -- (IBAction)moveToEndOfParagraph:(__unused id)sender { - if (self.zoomEnabled) { - [self setZoomLevel:round(self.zoomLevel) - 1 animated:YES]; - } -} - -- (IBAction)moveWordLeft:(__unused id)sender { - if (self.rotateEnabled) { - [self offsetDirectionBy:MGLKeyRotationIncrement animated:YES]; - } -} - -- (IBAction)moveWordRight:(__unused id)sender { - if (self.rotateEnabled) { - [self offsetDirectionBy:-MGLKeyRotationIncrement animated:YES]; - } -} - -- (void)setZoomEnabled:(BOOL)zoomEnabled { - _zoomEnabled = zoomEnabled; - _zoomControls.enabled = zoomEnabled; - _zoomControls.hidden = !zoomEnabled; -} - -- (void)setRotateEnabled:(BOOL)rotateEnabled { - _rotateEnabled = rotateEnabled; - _compass.enabled = rotateEnabled; - _compass.hidden = !rotateEnabled; -} - -#pragma mark Ornaments - -/// Updates the zoom controls’ enabled state based on the current zoom level. -- (void)updateZoomControls { - [_zoomControls setEnabled:self.zoomLevel > self.minimumZoomLevel forSegment:0]; - [_zoomControls setEnabled:self.zoomLevel < self.maximumZoomLevel forSegment:1]; -} - -/// Updates the compass to point in the same direction as the map. -- (void)updateCompass { - // The circular slider control goes counterclockwise, whereas our map - // measures its direction clockwise. - _compass.doubleValue = -self.direction; -} - -- (IBAction)rotate:(NSSlider *)sender { - [self setDirection:-sender.doubleValue animated:YES]; -} - -- (IBAction)giveFeedback:(id)sender { - MGLMapCamera *camera = self.camera; - double zoomLevel = self.zoomLevel; - NSMutableArray *urls = [NSMutableArray array]; - for (MGLAttributionInfo *info in [self.style attributionInfosWithFontSize:0 linkColor:nil]) { - NSURL *url = [info feedbackURLForStyleURL:self.styleURL - atCenterCoordinate:camera.centerCoordinate - zoomLevel:zoomLevel - direction:camera.heading - pitch:camera.pitch]; - if (url) { - [urls addObject:url]; - } - } - [[NSWorkspace sharedWorkspace] openURLs:urls - withAppBundleIdentifier:nil - options:0 - additionalEventParamDescriptor:nil - launchIdentifiers:nil]; -} - -#pragma mark Annotations - -- (nullable NSArray<id <MGLAnnotation>> *)annotations { - if (_annotationContextsByAnnotationTag.empty()) { - return nil; - } - - // Map all the annotation tags to the annotations themselves. - std::vector<id <MGLAnnotation>> annotations; - std::transform(_annotationContextsByAnnotationTag.begin(), - _annotationContextsByAnnotationTag.end(), - std::back_inserter(annotations), - ^ id <MGLAnnotation> (const std::pair<MGLAnnotationTag, MGLAnnotationContext> &pair) { - return pair.second.annotation; - }); - return [NSArray arrayWithObjects:&annotations[0] count:annotations.size()]; -} - -- (nullable NSArray<id <MGLAnnotation>> *)visibleAnnotations -{ - return [self visibleAnnotationsInRect:self.bounds]; -} - -- (nullable NSArray<id <MGLAnnotation>> *)visibleAnnotationsInRect:(CGRect)rect -{ - if (_annotationContextsByAnnotationTag.empty()) - { - return nil; - } - - std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:rect]; - std::vector<MGLAnnotationTag> shapeAnnotationTags = [self shapeAnnotationTagsInRect:rect]; - - if (shapeAnnotationTags.size()) { - annotationTags.insert(annotationTags.end(), shapeAnnotationTags.begin(), shapeAnnotationTags.end()); - } - - if (annotationTags.size()) - { - NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()]; - - for (auto const& annotationTag: annotationTags) - { - if (!_annotationContextsByAnnotationTag.count(annotationTag) || - annotationTag == MGLAnnotationTagNotFound) - { - continue; - } - - MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - NSAssert(annotationContext.annotation, @"Missing annotation for tag %llu.", annotationTag); - if (annotationContext.annotation) - { - [annotations addObject:annotationContext.annotation]; - } - } - - return [annotations copy]; - } - - return nil; -} - -/// Returns the annotation assigned the given tag. Cheap. -- (id <MGLAnnotation>)annotationWithTag:(MGLAnnotationTag)tag { - if ( ! _annotationContextsByAnnotationTag.count(tag) || - tag == MGLAnnotationTagNotFound) { - return nil; - } - - MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(tag); - return annotationContext.annotation; -} - -/// Returns the annotation tag assigned to the given annotation. -- (MGLAnnotationTag)annotationTagForAnnotation:(id <MGLAnnotation>)annotation { - if (!annotation || _annotationTagsByAnnotation.count(annotation) == 0) { - return MGLAnnotationTagNotFound; - } - - return _annotationTagsByAnnotation.at(annotation); -} - -- (void)addAnnotation:(id <MGLAnnotation>)annotation { - if (annotation) { - [self addAnnotations:@[annotation]]; - } -} - -- (void)addAnnotations:(NSArray<id <MGLAnnotation>> *)annotations { - if (!annotations) { - return; - } - - [self willChangeValueForKey:@"annotations"]; - - BOOL delegateHasImagesForAnnotations = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)]; - - for (id <MGLAnnotation> annotation in annotations) { - NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation"); - - // adding the same annotation object twice is a no-op - if (_annotationTagsByAnnotation.count(annotation) != 0) { - continue; - } - - if ([annotation isKindOfClass:[MGLMultiPoint class]]) { - // The multipoint knows how to style itself (with the map view’s help). - MGLMultiPoint *multiPoint = (MGLMultiPoint *)annotation; - if (!multiPoint.pointCount) { - continue; - } - - _isChangingAnnotationLayers = YES; - MGLAnnotationTag annotationTag = _mbglMap->addAnnotation([multiPoint annotationObjectWithDelegate:self]); - MGLAnnotationContext context; - context.annotation = annotation; - _annotationContextsByAnnotationTag[annotationTag] = context; - _annotationTagsByAnnotation[annotation] = annotationTag; - - [(NSObject *)annotation addObserver:self forKeyPath:@"coordinates" options:0 context:(void *)(NSUInteger)annotationTag]; - } else if (![annotation isKindOfClass:[MGLMultiPolyline class]] - && ![annotation isKindOfClass:[MGLMultiPolygon class]] - && ![annotation isKindOfClass:[MGLShapeCollection class]] - && ![annotation isKindOfClass:[MGLPointCollection class]]) { - MGLAnnotationImage *annotationImage = nil; - if (delegateHasImagesForAnnotations) { - annotationImage = [self.delegate mapView:self imageForAnnotation:annotation]; - } - if (!annotationImage) { - annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName]; - } - if (!annotationImage) { - annotationImage = self.defaultAnnotationImage; - } - - NSString *symbolName = annotationImage.styleIconIdentifier; - if (!symbolName) { - symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; - annotationImage.styleIconIdentifier = symbolName; - } - - if (!self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) { - self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; - [self installAnnotationImage:annotationImage]; - } - - MGLAnnotationTag annotationTag = _mbglMap->addAnnotation(mbgl::SymbolAnnotation { - MGLPointFromLocationCoordinate2D(annotation.coordinate), - symbolName.UTF8String ?: "" - }); - - MGLAnnotationContext context; - context.annotation = annotation; - context.imageReuseIdentifier = annotationImage.reuseIdentifier; - _annotationContextsByAnnotationTag[annotationTag] = context; - _annotationTagsByAnnotation[annotation] = annotationTag; - - if ([annotation isKindOfClass:[NSObject class]]) { - NSAssert(![annotation isKindOfClass:[MGLMultiPoint class]], @"Point annotation should not be MGLMultiPoint."); - [(NSObject *)annotation addObserver:self forKeyPath:@"coordinate" options:0 context:(void *)(NSUInteger)annotationTag]; - } - - // Opt into potentially expensive tooltip tracking areas. - if ([annotation respondsToSelector:@selector(toolTip)] && annotation.toolTip.length) { - _wantsToolTipRects = YES; - } - } - } - - [self didChangeValueForKey:@"annotations"]; - if (_isChangingAnnotationLayers) { - [self.style willChangeValueForKey:@"layers"]; - } - - [self updateAnnotationTrackingAreas]; -} - -/// Initializes and returns a default annotation image that depicts a round pin -/// rising from the center, with a shadow slightly below center. The alignment -/// rect therefore excludes the bottom half. -- (MGLAnnotationImage *)defaultAnnotationImage { - NSImage *image = MGLDefaultMarkerImage(); - NSRect alignmentRect = image.alignmentRect; - alignmentRect.origin.y = NSMidY(alignmentRect); - alignmentRect.size.height /= 2; - image.alignmentRect = alignmentRect; - return [MGLAnnotationImage annotationImageWithImage:image - reuseIdentifier:MGLDefaultStyleMarkerSymbolName]; -} - -/// Sends the raw pixel data of the annotation image’s image to mbgl and -/// calculates state needed for hit testing later. -- (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage { - NSString *iconIdentifier = annotationImage.styleIconIdentifier; - self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; - - NSImage *image = annotationImage.image; - NSSize size = image.size; - if (size.width == 0 || size.height == 0 || !image.valid) { - // Can’t create an empty sprite. An image that hasn’t loaded is also useless. - return; - } - - _mbglMap->addAnnotationImage([annotationImage.image mgl_styleImageWithIdentifier:iconIdentifier]); - - // Create a slop area with a “radius” equal to the annotation image’s entire - // size, allowing the eventual click to be on any point within this image. - // Union this slop area with any existing slop areas. - _unionedAnnotationImageSize = NSMakeSize(MAX(_unionedAnnotationImageSize.width, size.width), - MAX(_unionedAnnotationImageSize.height, size.height)); - - // Opt into potentially expensive cursor tracking areas. - if (annotationImage.cursor) { - _wantsCursorRects = YES; - } -} - -- (void)removeAnnotation:(id <MGLAnnotation>)annotation { - if (annotation) { - [self removeAnnotations:@[annotation]]; - } -} - -- (void)removeAnnotations:(NSArray<id <MGLAnnotation>> *)annotations { - if (!annotations) { - return; - } - - [self willChangeValueForKey:@"annotations"]; - - for (id <MGLAnnotation> annotation in annotations) { - NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation"); - - MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; - NSAssert(annotationTag != MGLAnnotationTagNotFound, @"No ID for annotation %@", annotation); - - if (annotationTag == _selectedAnnotationTag) { - [self deselectAnnotation:annotation]; - } - if (annotationTag == _lastSelectedAnnotationTag) { - _lastSelectedAnnotationTag = MGLAnnotationTagNotFound; - } - - _annotationContextsByAnnotationTag.erase(annotationTag); - _annotationTagsByAnnotation.erase(annotation); - - if ([annotation isKindOfClass:[NSObject class]] && - ![annotation isKindOfClass:[MGLMultiPoint class]]) { - [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinate" context:(void *)(NSUInteger)annotationTag]; - } else if ([annotation isKindOfClass:[MGLMultiPoint class]]) { - [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinates" context:(void *)(NSUInteger)annotationTag]; - } - - _isChangingAnnotationLayers = YES; - _mbglMap->removeAnnotation(annotationTag); - } - - [self didChangeValueForKey:@"annotations"]; - if (_isChangingAnnotationLayers) { - [self.style willChangeValueForKey:@"layers"]; - } - - [self updateAnnotationTrackingAreas]; -} - -- (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier { - return self.annotationImagesByIdentifier[identifier]; -} - -- (id <MGLAnnotation>)annotationAtPoint:(NSPoint)point { - return [self annotationWithTag:[self annotationTagAtPoint:point persistingResults:NO]]; -} - -/** - Returns the tag of the annotation at the given point in the view. - - This is more involved than it sounds: if multiple point annotations overlap - near the point, this method cycles through them so that each of them is - accessible to the user at some point. - - @param persist True to remember the cycleable set of annotations, so that a - different annotation is returned the next time this method is called - with the same point. Setting this parameter to false is useful for - asking “what if?” - */ -- (MGLAnnotationTag)annotationTagAtPoint:(NSPoint)point persistingResults:(BOOL)persist { - // Look for any annotation near the click. An annotation is “near” if the - // distance between its center and the click is less than the maximum height - // or width of an installed annotation image. - NSRect queryRect = NSInsetRect({ point, NSZeroSize }, - -_unionedAnnotationImageSize.width / 2, - -_unionedAnnotationImageSize.height / 2); - queryRect = NSInsetRect(queryRect, -MGLAnnotationImagePaddingForHitTest, - -MGLAnnotationImagePaddingForHitTest); - std::vector<MGLAnnotationTag> nearbyAnnotations = [self annotationTagsInRect:queryRect]; - std::vector<MGLAnnotationTag> nearbyShapeAnnotations = [self shapeAnnotationTagsInRect:queryRect]; - - if (nearbyShapeAnnotations.size()) { - nearbyAnnotations.insert(nearbyAnnotations.end(), nearbyShapeAnnotations.begin(), nearbyShapeAnnotations.end()); - } - - if (nearbyAnnotations.size()) { - // Assume that the user is fat-fingering an annotation. - NSRect hitRect = NSInsetRect({ point, NSZeroSize }, - -MGLAnnotationImagePaddingForHitTest, - -MGLAnnotationImagePaddingForHitTest); - - // Filter out any annotation whose image is unselectable or for which - // hit testing fails. - auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag) { - id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag]; - NSAssert(annotation, @"Unknown annotation found nearby click"); - if (!annotation) { - return true; - } - - if ([annotation isKindOfClass:[MGLMultiPoint class]]) - { - if ([self.delegate respondsToSelector:@selector(mapView:shapeAnnotationIsEnabled:)]) { - return !!(![self.delegate mapView:self shapeAnnotationIsEnabled:(MGLMultiPoint *)annotation]); - } else { - return false; - } - } - - MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - if (!annotationImage.selectable) { - return true; - } - - // Filter out the annotation if the fattened finger didn’t land on a - // translucent or opaque pixel in the image. - NSRect annotationRect = [self frameOfImage:annotationImage.image - centeredAtCoordinate:annotation.coordinate]; - return !!![annotationImage.image hitTestRect:hitRect withImageDestinationRect:annotationRect - context:nil hints:nil flipped:NO]; - }); - nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); - } - - MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound; - if (nearbyAnnotations.size()) { - // The first selection in the cycle should be the one nearest to the - // tap. Also the annotation tags need to be stable in order to compare them with - // the remembered tags _annotationsNearbyLastClick. - CLLocationCoordinate2D currentCoordinate = [self convertPoint:point toCoordinateFromView:self]; - std::sort(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag tagA, const MGLAnnotationTag tagB) { - CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate]; - CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate]; - CLLocationDegrees deltaA = hypot(coordinateA.latitude - currentCoordinate.latitude, - coordinateA.longitude - currentCoordinate.longitude); - CLLocationDegrees deltaB = hypot(coordinateB.latitude - currentCoordinate.latitude, - coordinateB.longitude - currentCoordinate.longitude); - return deltaA < deltaB; - }); - - if (nearbyAnnotations == _annotationsNearbyLastClick) { - // The last time we persisted a set of annotations, we had the same - // set of annotations as we do now. Cycle through them. - if (_lastSelectedAnnotationTag == MGLAnnotationTagNotFound - || _lastSelectedAnnotationTag == nearbyAnnotations.back()) { - // Either no annotation is selected or the last annotation in - // the set was selected. Wrap around to the first annotation in - // the set. - hitAnnotationTag = nearbyAnnotations.front(); - } else { - auto result = std::find(nearbyAnnotations.begin(), - nearbyAnnotations.end(), - _lastSelectedAnnotationTag); - if (result == nearbyAnnotations.end()) { - // An annotation from this set hasn’t been selected before. - // Select the first (nearest) one. - hitAnnotationTag = nearbyAnnotations.front(); - } else { - // Step to the next annotation in the set. - auto distance = std::distance(nearbyAnnotations.begin(), result); - hitAnnotationTag = nearbyAnnotations[distance + 1]; - } - } - } else { - // Remember the nearby annotations for the next time this method is - // called. - if (persist) { - _annotationsNearbyLastClick = nearbyAnnotations; - } - - // Choose the first nearby annotation. - if (nearbyAnnotations.size()) { - hitAnnotationTag = nearbyAnnotations.front(); - } - } - } - - return hitAnnotationTag; -} - -/// Returns the tags of the annotations coincident with the given rectangle. -- (std::vector<MGLAnnotationTag>)annotationTagsInRect:(NSRect)rect { - // Cocoa origin is at the lower-left corner. - return self.renderer->queryPointAnnotations({ - { NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) }, - { NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) }, - }); -} - -- (std::vector<MGLAnnotationTag>)shapeAnnotationTagsInRect:(NSRect)rect { - // Cocoa origin is at the lower-left corner. - return _rendererFrontend->getRenderer()->queryShapeAnnotations({ - { NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) }, - { NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) }, - }); -} - -- (id <MGLAnnotation>)selectedAnnotation { - if ( ! _annotationContextsByAnnotationTag.count(_selectedAnnotationTag) || - _selectedAnnotationTag == MGLAnnotationTagNotFound) { - return nil; - } - - MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(_selectedAnnotationTag); - return annotationContext.annotation; -} - -- (void)setSelectedAnnotation:(id <MGLAnnotation>)annotation { - MGLLogDebug(@"Selecting annotation: %@", annotation); - [self willChangeValueForKey:@"selectedAnnotations"]; - _selectedAnnotationTag = [self annotationTagForAnnotation:annotation]; - if (_selectedAnnotationTag != MGLAnnotationTagNotFound) { - _lastSelectedAnnotationTag = _selectedAnnotationTag; - } - [self didChangeValueForKey:@"selectedAnnotations"]; -} - -- (NSArray<id <MGLAnnotation>> *)selectedAnnotations { - id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation; - return selectedAnnotation ? @[selectedAnnotation] : @[]; -} - -- (void)setSelectedAnnotations:(NSArray<id <MGLAnnotation>> *)selectedAnnotations { - MGLLogDebug(@"Selecting: %lu annotations", selectedAnnotations.count); - if (!selectedAnnotations.count) { - return; - } - - id <MGLAnnotation> firstAnnotation = selectedAnnotations[0]; - NSAssert([firstAnnotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation"); - if ([firstAnnotation isKindOfClass:[MGLMultiPoint class]]) { - return; - } - - [self selectAnnotation:firstAnnotation]; -} - -- (BOOL)isMovingAnnotationIntoViewSupportedForAnnotation:(id<MGLAnnotation>)annotation animated:(BOOL)animated { - // Consider delegating - return [annotation isKindOfClass:[MGLPointAnnotation class]]; -} - -- (void)selectAnnotation:(id <MGLAnnotation>)annotation -{ - MGLLogDebug(@"Selecting annotation: %@", annotation); - [self selectAnnotation:annotation atPoint:NSZeroPoint]; -} - -- (void)selectAnnotation:(id <MGLAnnotation>)annotation atPoint:(NSPoint)gesturePoint -{ - MGLLogDebug(@"Selecting annotation: %@ atPoint: %@", annotation, NSStringFromPoint(gesturePoint)); - [self selectAnnotation:annotation atPoint:gesturePoint moveIntoView:YES animateSelection:YES]; -} - -- (void)selectAnnotation:(id <MGLAnnotation>)annotation atPoint:(NSPoint)gesturePoint moveIntoView:(BOOL)moveIntoView animateSelection:(BOOL)animateSelection -{ - MGLLogDebug(@"Selecting annotation: %@ atPoint: %@ moveIntoView: %@ animateSelection: %@", annotation, NSStringFromPoint(gesturePoint), MGLStringFromBOOL(moveIntoView), MGLStringFromBOOL(animateSelection)); - id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation; - if (annotation == selectedAnnotation) { - return; - } - - // Deselect the annotation before reselecting it. - [self deselectAnnotation:selectedAnnotation]; - - // Add the annotation to the map if it hasn’t been added yet. - MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; - if (annotationTag == MGLAnnotationTagNotFound) { - [self addAnnotation:annotation]; - } - - if (moveIntoView) { - moveIntoView = [self isMovingAnnotationIntoViewSupportedForAnnotation:annotation animated:animateSelection]; - } - - // The annotation's anchor will bounce to the current click. - NSRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag]; - - // Check for invalid (zero) positioning rect - if (NSEqualRects(positioningRect, NSZeroRect)) { - CLLocationCoordinate2D origin = annotation.coordinate; - positioningRect.origin = [self convertCoordinate:origin toPointToView:self]; - } - - BOOL shouldShowCallout = ([annotation respondsToSelector:@selector(title)] - && annotation.title - && !self.calloutForSelectedAnnotation.shown - && [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] - && [self.delegate mapView:self annotationCanShowCallout:annotation]); - - if (NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) { - if (!moveIntoView && !NSEqualPoints(gesturePoint, NSZeroPoint)) { - positioningRect = CGRectMake(gesturePoint.x, gesturePoint.y, positioningRect.size.width, positioningRect.size.height); - } - } - // Onscreen or partially on-screen - else if (!shouldShowCallout) { - moveIntoView = NO; - } - - self.selectedAnnotation = annotation; - - // For the callout to be shown, the annotation must have a title, its - // callout must not already be shown, and the annotation must be able to - // show a callout according to the delegate. - if (shouldShowCallout) { - NSPopover *callout = [self calloutForAnnotation:annotation]; - - // Hang the callout off the right edge of the annotation image’s - // alignment rect, or off the left edge in a right-to-left UI. - callout.delegate = self; - self.calloutForSelectedAnnotation = callout; - - NSRectEdge edge = (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft - ? NSMinXEdge - : NSMaxXEdge); - - // The following will do nothing if the positioning rect is not on-screen. See - // `-[MGLMapView updateAnnotationCallouts]` for presenting the callout when the selected - // annotation comes back on-screen. - [callout showRelativeToRect:positioningRect ofView:self preferredEdge:edge]; - } - - if (moveIntoView) - { - moveIntoView = NO; - - NSRect (^edgeInsetsInsetRect)(NSRect, NSEdgeInsets) = ^(NSRect rect, NSEdgeInsets insets) { - return NSMakeRect(rect.origin.x + insets.left, - rect.origin.y + insets.bottom, - rect.size.width - insets.left - insets.right, - rect.size.height - insets.top - insets.bottom); - }; - - // Add padding around the positioning rect (in essence an inset from the edge of the viewport - NSRect expandedPositioningRect = positioningRect; - - if (shouldShowCallout) { - // If we have a callout, expand this rect to include a buffer - expandedPositioningRect = edgeInsetsInsetRect(positioningRect, MGLMapViewOffscreenAnnotationPadding); - } - - // Used for callout positioning, and moving offscreen annotations onscreen. - CGRect constrainedRect = edgeInsetsInsetRect(self.bounds, self.contentInsets); - CGRect bounds = constrainedRect; - - // Any one of these cases should trigger a move onscreen - CGFloat minX = CGRectGetMinX(expandedPositioningRect); - - if (minX < CGRectGetMinX(bounds)) { - constrainedRect.origin.x = minX; - moveIntoView = YES; - } - else { - CGFloat maxX = CGRectGetMaxX(expandedPositioningRect); - - if (maxX > CGRectGetMaxX(bounds)) { - constrainedRect.origin.x = maxX - CGRectGetWidth(constrainedRect); - moveIntoView = YES; - } - } - - CGFloat minY = CGRectGetMinY(expandedPositioningRect); - - if (minY < CGRectGetMinY(bounds)) { - constrainedRect.origin.y = minY; - moveIntoView = YES; - } - else { - CGFloat maxY = CGRectGetMaxY(expandedPositioningRect); - - if (maxY > CGRectGetMaxY(bounds)) { - constrainedRect.origin.y = maxY - CGRectGetHeight(constrainedRect); - moveIntoView = YES; - } - } - - if (moveIntoView) - { - CGPoint center = CGPointMake(CGRectGetMidX(constrainedRect), CGRectGetMidY(constrainedRect)); - CLLocationCoordinate2D centerCoord = [self convertPoint:center toCoordinateFromView:self]; - [self setCenterCoordinate:centerCoord animated:animateSelection]; - } - } -} - -- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations animated:(BOOL)animated { - MGLLogDebug(@"Showing: %lu annotations animated: %@", annotations.count, MGLStringFromBOOL(animated)); - CGFloat maximumPadding = 100; - CGFloat yPadding = (NSHeight(self.bounds) / 5 <= maximumPadding) ? (NSHeight(self.bounds) / 5) : maximumPadding; - CGFloat xPadding = (NSWidth(self.bounds) / 5 <= maximumPadding) ? (NSWidth(self.bounds) / 5) : maximumPadding; - - NSEdgeInsets edgeInsets = NSEdgeInsetsMake(yPadding, xPadding, yPadding, xPadding); - - [self showAnnotations:annotations edgePadding:edgeInsets animated:animated]; -} - -- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated { - [self showAnnotations:annotations edgePadding:insets animated:animated completionHandler:nil]; -} - -- (void)showAnnotations:(NSArray<id <MGLAnnotation>> *)annotations edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion { - if (!annotations.count) { - if (completion) { - completion(); - } - return; - } - - mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - - for (id <MGLAnnotation> annotation in annotations) { - if ([annotation conformsToProtocol:@protocol(MGLOverlay)]) { - bounds.extend(MGLLatLngBoundsFromCoordinateBounds(((id <MGLOverlay>)annotation).overlayBounds)); - } else { - bounds.extend(MGLLatLngFromLocationCoordinate2D(annotation.coordinate)); - } - } - - [self setVisibleCoordinateBounds:MGLCoordinateBoundsFromLatLngBounds(bounds) - edgePadding:insets - animated:animated - completionHandler:completion]; -} - -/// Returns a popover detailing the annotation. -- (NSPopover *)calloutForAnnotation:(id <MGLAnnotation>)annotation { - NSPopover *callout = [[NSPopover alloc] init]; - callout.behavior = NSPopoverBehaviorTransient; - - NSViewController *viewController; - if ([self.delegate respondsToSelector:@selector(mapView:calloutViewControllerForAnnotation:)]) { - NSViewController *viewControllerFromDelegate = [self.delegate mapView:self - calloutViewControllerForAnnotation:annotation]; - if (viewControllerFromDelegate) { - viewController = viewControllerFromDelegate; - } - } - if (!viewController) { - viewController = self.calloutViewController; - } - NSAssert(viewController, @"Unable to load MGLAnnotationCallout view controller"); - // The popover’s view controller can bind to KVO-compliant key paths of the - // annotation. - viewController.representedObject = annotation; - callout.contentViewController = viewController; - - return callout; -} - -- (NSViewController *)calloutViewController { - // Lazily load a default view controller. - if (!_calloutViewController) { - _calloutViewController = [[NSViewController alloc] initWithNibName:@"MGLAnnotationCallout" - bundle:[NSBundle mgl_frameworkBundle]]; - } - return _calloutViewController; -} - -/// Returns the rectangle that represents the annotation image of the annotation -/// with the given tag. This rectangle is fitted to the image’s alignment rect -/// and is appropriate for positioning a popover. -- (NSRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annotationTag { - id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag]; - if (!annotation) { - return NSZeroRect; - } - if ([annotation isKindOfClass:[MGLMultiPoint class]]) { - CLLocationCoordinate2D origin = annotation.coordinate; - CGPoint originPoint = [self convertCoordinate:origin toPointToView:self]; - return CGRectMake(originPoint.x, originPoint.y, MGLAnnotationImagePaddingForHitTest, MGLAnnotationImagePaddingForHitTest); - - } - - NSImage *image = [self imageOfAnnotationWithTag:annotationTag].image; - if (!image) { - image = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName].image; - } - if (!image) { - return NSZeroRect; - } - - NSRect positioningRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate]; - positioningRect = NSOffsetRect(image.alignmentRect, positioningRect.origin.x, positioningRect.origin.y); - return NSInsetRect(positioningRect, -MGLAnnotationImagePaddingForCallout, - -MGLAnnotationImagePaddingForCallout); -} - -/// Returns the rectangle relative to the viewport that represents the given -/// image centered at the given coordinate. -- (NSRect)frameOfImage:(NSImage *)image centeredAtCoordinate:(CLLocationCoordinate2D)coordinate { - NSPoint calloutAnchorPoint = [self convertCoordinate:coordinate toPointToView:self]; - return NSInsetRect({ calloutAnchorPoint, NSZeroSize }, -image.size.width / 2, -image.size.height / 2); -} - -/// Returns the annotation image assigned to the annotation with the given tag. -- (MGLAnnotationImage *)imageOfAnnotationWithTag:(MGLAnnotationTag)annotationTag { - if (annotationTag == MGLAnnotationTagNotFound - || _annotationContextsByAnnotationTag.count(annotationTag) == 0) { - return nil; - } - - NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier; - NSString *symbolName = customSymbol.length ? customSymbol : MGLDefaultStyleMarkerSymbolName; - - return [self dequeueReusableAnnotationImageWithIdentifier:symbolName]; -} - -- (void)deselectAnnotation:(id <MGLAnnotation>)annotation { - if (!annotation || self.selectedAnnotation != annotation) { - return; - } - - // Close the callout popover gracefully. - NSPopover *callout = self.calloutForSelectedAnnotation; - [callout performClose:self]; - - self.selectedAnnotation = nil; -} - -/// Move the annotation callout to point to the selected annotation at its -/// current position. -- (void)updateAnnotationCallouts { - NSPopover *callout = self.calloutForSelectedAnnotation; - if (callout) { - NSRect rect = [self positioningRectForCalloutForAnnotationWithTag:_selectedAnnotationTag]; - - NSAssert(!NSEqualRects(rect, NSZeroRect), @"Positioning rect should be non-zero"); - - if (!NSIsEmptyRect(NSIntersectionRect(rect, self.bounds))) { - - // It's possible that the current callout hasn't been presented (since the original - // positioningRect was offscreen). We can check that the callout has a valid window - // This results in the callout being presented just as the annotation comes on screen - // which matches MapKit, but (currently) not iOS. - if (!callout.contentViewController.view.window) { - NSRectEdge edge = (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft - ? NSMinXEdge - : NSMaxXEdge); - // Re-present the callout - [callout showRelativeToRect:rect ofView:self preferredEdge:edge]; - } - else { - callout.positioningRect = rect; - } - } - } -} - -#pragma mark MGLMultiPointDelegate methods - -- (double)alphaForShapeAnnotation:(MGLShape *)annotation { - if (_delegateHasAlphasForShapeAnnotations) { - return [self.delegate mapView:self alphaForShapeAnnotation:annotation]; - } - return 1.0; -} - -- (mbgl::Color)strokeColorForShapeAnnotation:(MGLShape *)annotation { - NSColor *color = (_delegateHasStrokeColorsForShapeAnnotations - ? [self.delegate mapView:self strokeColorForShapeAnnotation:annotation] - : [NSColor selectedMenuItemColor]); - return color.mgl_color; -} - -- (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation { - NSColor *color = (_delegateHasFillColorsForShapeAnnotations - ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation] - : [NSColor selectedMenuItemColor]); - return color.mgl_color; -} - -- (CGFloat)lineWidthForPolylineAnnotation:(MGLPolyline *)annotation { - if (_delegateHasLineWidthsForShapeAnnotations) { - return [self.delegate mapView:self lineWidthForPolylineAnnotation:(MGLPolyline *)annotation]; - } - return 3.0; -} - -#pragma mark MGLPopoverDelegate methods - -- (void)popoverDidShow:(__unused NSNotification *)notification { - id <MGLAnnotation> annotation = self.selectedAnnotation; - if (annotation && [self.delegate respondsToSelector:@selector(mapView:didSelectAnnotation:)]) { - [self.delegate mapView:self didSelectAnnotation:annotation]; - } -} - -- (void)popoverDidClose:(__unused NSNotification *)notification { - // Deselect the closed popover, in case the popover was closed due to user - // action. - id <MGLAnnotation> annotation = self.calloutForSelectedAnnotation.contentViewController.representedObject; - self.calloutForSelectedAnnotation = nil; - self.selectedAnnotation = nil; - - if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)]) { - [self.delegate mapView:self didDeselectAnnotation:annotation]; - } -} - -#pragma mark Overlays - -- (nonnull NSArray<id <MGLOverlay>> *)overlays -{ - if (self.annotations == nil) { return @[]; } - - NSMutableArray<id <MGLOverlay>> *mutableOverlays = [NSMutableArray array]; - - [self.annotations enumerateObjectsUsingBlock:^(id<MGLAnnotation> _Nonnull annotation, NSUInteger idx, BOOL * _Nonnull stop) { - if ([annotation conformsToProtocol:@protocol(MGLOverlay)]) - { - [mutableOverlays addObject:(id<MGLOverlay>)annotation]; - } - }]; - - return [NSArray arrayWithArray:mutableOverlays]; -} - -- (void)addOverlay:(id <MGLOverlay>)overlay { - MGLLogDebug(@"Adding overlay: %@", overlay); - [self addOverlays:@[overlay]]; -} - -- (void)addOverlays:(NSArray<id <MGLOverlay>> *)overlays -{ - MGLLogDebug(@"Adding: %lu overlays", overlays.count); -#if DEBUG - for (id <MGLOverlay> overlay in overlays) { - NSAssert([overlay conformsToProtocol:@protocol(MGLOverlay)], @"Overlay does not conform to MGLOverlay"); - } -#endif - [self addAnnotations:overlays]; -} - -- (void)removeOverlay:(id <MGLOverlay>)overlay { - MGLLogDebug(@"Removing overlay: %@", overlay); - [self removeOverlays:@[overlay]]; -} - -- (void)removeOverlays:(NSArray<id <MGLOverlay>> *)overlays { - MGLLogDebug(@"Removing: %lu overlays", overlays.count); -#if DEBUG - for (id <MGLOverlay> overlay in overlays) { - NSAssert([overlay conformsToProtocol:@protocol(MGLOverlay)], @"Overlay does not conform to MGLOverlay"); - } -#endif - [self removeAnnotations:overlays]; -} - -#pragma mark Tooltips and cursors - -- (void)updateAnnotationTrackingAreas { - if (_wantsToolTipRects) { - [self removeAllToolTips]; - std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:self.bounds]; - for (MGLAnnotationTag annotationTag : annotationTags) { - MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag]; - if ([annotation respondsToSelector:@selector(toolTip)] && annotation.toolTip.length) { - // Add a tooltip tracking area over the annotation image’s - // frame, accounting for the image’s alignment rect. - NSImage *image = annotationImage.image; - NSRect annotationRect = [self frameOfImage:image - centeredAtCoordinate:annotation.coordinate]; - annotationRect = NSOffsetRect(image.alignmentRect, annotationRect.origin.x, annotationRect.origin.y); - if (!NSIsEmptyRect(annotationRect)) { - [self addToolTipRect:annotationRect owner:self userData:(void *)(NSUInteger)annotationTag]; - } - } - // Opt into potentially expensive cursor tracking areas. - if (annotationImage.cursor) { - _wantsCursorRects = YES; - } - } - } - - // Blow away any cursor tracking areas and rebuild them. That’s the - // potentially expensive part. - if (_wantsCursorRects) { - [self.window invalidateCursorRectsForView:self]; - } -} - -- (NSString *)view:(__unused NSView *)view stringForToolTip:(__unused NSToolTipTag)tag point:(__unused NSPoint)point userData:(void *)data { - NSAssert((NSUInteger)data < MGLAnnotationTagNotFound, @"Invalid annotation tag in tooltip rect user data."); - MGLAnnotationTag annotationTag = (MGLAnnotationTag)MIN((NSUInteger)data, MGLAnnotationTagNotFound); - id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag]; - return annotation.toolTip; -} - -- (void)resetCursorRects { - // Drag to pan has a grabbing hand cursor. - if ([self isPanningWithGesture]) { - [self addCursorRect:self.bounds cursor:[NSCursor closedHandCursor]]; - return; - } - - // The rest of this method can be expensive, so bail if no annotations have - // ever had custom cursors. - if (!_wantsCursorRects) { - return; - } - - std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:self.bounds]; - for (MGLAnnotationTag annotationTag : annotationTags) { - id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag]; - MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - if (annotationImage.cursor) { - // Add a cursor tracking area over the annotation image, respecting - // the image’s alignment rect. - NSImage *image = annotationImage.image; - NSRect annotationRect = [self frameOfImage:image - centeredAtCoordinate:annotation.coordinate]; - annotationRect = NSOffsetRect(image.alignmentRect, annotationRect.origin.x, annotationRect.origin.y); - [self addCursorRect:annotationRect cursor:annotationImage.cursor]; - } - } -} - -#pragma mark Data - -- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(NSPoint)point { - MGLLogDebug(@"Querying visibleFeaturesAtPoint: %@", NSStringFromPoint(point)); - return [self visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:nil]; -} - -- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(CGPoint)point inStyleLayersWithIdentifiers:(NSSet<NSString *> *)styleLayerIdentifiers { - MGLLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@", NSStringFromPoint(point), styleLayerIdentifiers); - return [self visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:styleLayerIdentifiers predicate:nil]; -} - -- (NSArray<id <MGLFeature>> *)visibleFeaturesAtPoint:(NSPoint)point inStyleLayersWithIdentifiers:(NSSet<NSString *> *)styleLayerIdentifiers predicate:(NSPredicate *)predicate { - MGLLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", NSStringFromPoint(point), styleLayerIdentifiers, predicate); - // Cocoa origin is at the lower-left corner. - mbgl::ScreenCoordinate screenCoordinate = { point.x, NSHeight(self.bounds) - point.y }; - - mbgl::optional<std::vector<std::string>> optionalLayerIDs; - if (styleLayerIdentifiers) { - __block std::vector<std::string> layerIDs; - layerIDs.reserve(styleLayerIdentifiers.count); - [styleLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) { - layerIDs.push_back(identifier.UTF8String); - }]; - optionalLayerIDs = layerIDs; - } - - mbgl::optional<mbgl::style::Filter> optionalFilter; - if (predicate) { - optionalFilter = predicate.mgl_filter; - } - - std::vector<mbgl::Feature> features = _rendererFrontend->getRenderer()->queryRenderedFeatures(screenCoordinate, { optionalLayerIDs, optionalFilter }); - return MGLFeaturesFromMBGLFeatures(features); -} - -- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(NSRect)rect { - MGLLogDebug(@"Querying visibleFeaturesInRect: %@", NSStringFromRect(rect)); - return [self visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:nil]; -} - -- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(CGRect)rect inStyleLayersWithIdentifiers:(NSSet<NSString *> *)styleLayerIdentifiers { - MGLLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@", NSStringFromRect(rect), styleLayerIdentifiers); - return [self visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:styleLayerIdentifiers predicate:nil]; -} - -- (NSArray<id <MGLFeature>> *)visibleFeaturesInRect:(NSRect)rect inStyleLayersWithIdentifiers:(NSSet<NSString *> *)styleLayerIdentifiers predicate:(NSPredicate *)predicate { - MGLLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", NSStringFromRect(rect), styleLayerIdentifiers, predicate); - // Cocoa origin is at the lower-left corner. - mbgl::ScreenBox screenBox = { - { NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) }, - { NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) }, - }; - - mbgl::optional<std::vector<std::string>> optionalLayerIDs; - if (styleLayerIdentifiers) { - __block std::vector<std::string> layerIDs; - layerIDs.reserve(styleLayerIdentifiers.count); - [styleLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) { - layerIDs.push_back(identifier.UTF8String); - }]; - optionalLayerIDs = layerIDs; - } - - mbgl::optional<mbgl::style::Filter> optionalFilter; - if (predicate) { - optionalFilter = predicate.mgl_filter; - } - - std::vector<mbgl::Feature> features = _rendererFrontend->getRenderer()->queryRenderedFeatures(screenBox, { optionalLayerIDs, optionalFilter }); - return MGLFeaturesFromMBGLFeatures(features); -} - -#pragma mark User interface validation - -- (BOOL)validateMenuItem:(NSMenuItem *)menuItem { - if (menuItem.action == @selector(giveFeedback:)) { - return YES; - } - return NO; -} - -#pragma mark Interface Builder methods - -- (void)prepareForInterfaceBuilder { - [super prepareForInterfaceBuilder]; - - // Color the background a glorious Mapbox teal. - self.layer.borderColor = [NSColor colorWithRed:59/255. - green:178/255. - blue:208/255. - alpha:0.8].CGColor; - self.layer.borderWidth = 2; - self.layer.backgroundColor = [NSColor colorWithRed:59/255. - green:178/255. - blue:208/255. - alpha:0.6].CGColor; - - // Place a playful marker right smack dab in the middle. - self.layer.contents = MGLDefaultMarkerImage(); - self.layer.contentsGravity = kCAGravityCenter; - self.layer.contentsScale = [NSScreen mainScreen].backingScaleFactor; -} - -#pragma mark Geometric methods - -- (NSPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable NSView *)view { - if (!CLLocationCoordinate2DIsValid(coordinate)) { - return NSMakePoint(NAN, NAN); - } - return [self convertLatLng:MGLLatLngFromLocationCoordinate2D(coordinate) toPointToView:view]; -} - -/// Converts a geographic coordinate to a point in the view’s coordinate system. -- (NSPoint)convertLatLng:(mbgl::LatLng)latLng toPointToView:(nullable NSView *)view { - mbgl::ScreenCoordinate pixel = _mbglMap->pixelForLatLng(latLng); - // Cocoa origin is at the lower-left corner. - pixel.y = NSHeight(self.bounds) - pixel.y; - return [self convertPoint:NSMakePoint(pixel.x, pixel.y) toView:view]; -} - -- (CLLocationCoordinate2D)convertPoint:(NSPoint)point toCoordinateFromView:(nullable NSView *)view { - return MGLLocationCoordinate2DFromLatLng([self convertPoint:point toLatLngFromView:view]); -} - -/// Converts a point in the view’s coordinate system to a geographic coordinate. -- (mbgl::LatLng)convertPoint:(NSPoint)point toLatLngFromView:(nullable NSView *)view { - NSPoint convertedPoint = [self convertPoint:point fromView:view]; - return _mbglMap->latLngForPixel({ - convertedPoint.x, - // mbgl origin is at the top-left corner. - NSHeight(self.bounds) - convertedPoint.y, - }).wrapped(); -} - -- (NSRect)convertCoordinateBounds:(MGLCoordinateBounds)bounds toRectToView:(nullable NSView *)view { - return [self convertLatLngBounds:MGLLatLngBoundsFromCoordinateBounds(bounds) toRectToView:view]; -} - -/// Converts a geographic bounding box to a rectangle in the view’s coordinate -/// system. -- (NSRect)convertLatLngBounds:(mbgl::LatLngBounds)bounds toRectToView:(nullable NSView *)view { - auto northwest = bounds.northwest(); - auto northeast = bounds.northeast(); - auto southwest = bounds.southwest(); - auto southeast = bounds.southeast(); - - auto center = [self convertPoint:{ NSMidX(view.bounds), NSMidY(view.bounds) } toLatLngFromView:view]; - - // Extend bounds to account for the antimeridian - northwest.unwrapForShortestPath(center); - northeast.unwrapForShortestPath(center); - southwest.unwrapForShortestPath(center); - southeast.unwrapForShortestPath(center); - - auto correctedLatLngBounds = mbgl::LatLngBounds::empty(); - correctedLatLngBounds.extend(northwest); - correctedLatLngBounds.extend(northeast); - correctedLatLngBounds.extend(southwest); - correctedLatLngBounds.extend(southeast); - - NSRect rect = { [self convertLatLng:correctedLatLngBounds.southwest() toPointToView:view], CGSizeZero }; - rect = MGLExtendRect(rect, [self convertLatLng:correctedLatLngBounds.northeast() toPointToView:view]); - return rect; -} - -- (MGLCoordinateBounds)convertRect:(NSRect)rect toCoordinateBoundsFromView:(nullable NSView *)view { - return MGLCoordinateBoundsFromLatLngBounds([self convertRect:rect toLatLngBoundsFromView:view]); -} - -/// Converts a rectangle in the given view’s coordinate system to a geographic -/// bounding box. -- (mbgl::LatLngBounds)convertRect:(NSRect)rect toLatLngBoundsFromView:(nullable NSView *)view { - auto bounds = mbgl::LatLngBounds::empty(); - auto bottomLeft = [self convertPoint:{ NSMinX(rect), NSMinY(rect) } toLatLngFromView:view]; - auto bottomRight = [self convertPoint:{ NSMaxX(rect), NSMinY(rect) } toLatLngFromView:view]; - auto topRight = [self convertPoint:{ NSMaxX(rect), NSMaxY(rect) } toLatLngFromView:view]; - auto topLeft = [self convertPoint:{ NSMinX(rect), NSMaxY(rect) } toLatLngFromView:view]; - - // If the bounds straddles the antimeridian, unwrap it so that one side - // extends beyond ±180° longitude. - auto center = [self convertPoint:{ NSMidX(rect), NSMidY(rect) } toLatLngFromView:view]; - bottomLeft.unwrapForShortestPath(center); - bottomRight.unwrapForShortestPath(center); - topRight.unwrapForShortestPath(center); - topLeft.unwrapForShortestPath(center); - - bounds.extend(bottomLeft); - bounds.extend(bottomRight); - bounds.extend(topRight); - bounds.extend(topLeft); - - return bounds; -} - -- (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude { - return mbgl::Projection::getMetersPerPixelAtLatitude(latitude, self.zoomLevel); -} - -#pragma mark Debugging - -- (MGLMapDebugMaskOptions)debugMask { - mbgl::MapDebugOptions options = _mbglMap->getDebug(); - MGLMapDebugMaskOptions mask = 0; - if (options & mbgl::MapDebugOptions::TileBorders) { - mask |= MGLMapDebugTileBoundariesMask; - } - if (options & mbgl::MapDebugOptions::ParseStatus) { - mask |= MGLMapDebugTileInfoMask; - } - if (options & mbgl::MapDebugOptions::Timestamps) { - mask |= MGLMapDebugTimestampsMask; - } - if (options & mbgl::MapDebugOptions::Collision) { - mask |= MGLMapDebugCollisionBoxesMask; - } - if (options & mbgl::MapDebugOptions::Overdraw) { - mask |= MGLMapDebugOverdrawVisualizationMask; - } - if (options & mbgl::MapDebugOptions::StencilClip) { - mask |= MGLMapDebugStencilBufferMask; - } - if (options & mbgl::MapDebugOptions::DepthBuffer) { - mask |= MGLMapDebugDepthBufferMask; - } - return mask; -} - -- (void)setDebugMask:(MGLMapDebugMaskOptions)debugMask { - mbgl::MapDebugOptions options = mbgl::MapDebugOptions::NoDebug; - if (debugMask & MGLMapDebugTileBoundariesMask) { - options |= mbgl::MapDebugOptions::TileBorders; - } - if (debugMask & MGLMapDebugTileInfoMask) { - options |= mbgl::MapDebugOptions::ParseStatus; - } - if (debugMask & MGLMapDebugTimestampsMask) { - options |= mbgl::MapDebugOptions::Timestamps; - } - if (debugMask & MGLMapDebugCollisionBoxesMask) { - options |= mbgl::MapDebugOptions::Collision; - } - if (debugMask & MGLMapDebugOverdrawVisualizationMask) { - options |= mbgl::MapDebugOptions::Overdraw; - } - if (debugMask & MGLMapDebugStencilBufferMask) { - options |= mbgl::MapDebugOptions::StencilClip; - } - if (debugMask & MGLMapDebugDepthBufferMask) { - options |= mbgl::MapDebugOptions::DepthBuffer; - } - _mbglMap->setDebug(options); -} - -@end diff --git a/platform/macos/src/MGLMapViewDelegate.h b/platform/macos/src/MGLMapViewDelegate.h deleted file mode 100644 index 098164cd75..0000000000 --- a/platform/macos/src/MGLMapViewDelegate.h +++ /dev/null @@ -1,352 +0,0 @@ -#import <Foundation/Foundation.h> - -NS_ASSUME_NONNULL_BEGIN - -@class MGLMapView; -@class MGLAnnotationImage; -@class MGLPolygon; -@class MGLPolyline; -@class MGLShape; - -/** - The `MGLMapViewDelegate` protocol defines a set of optional methods that you - can use to receive messages from an `MGLMapView` instance. Because many map - operations require the `MGLMapView` class to load data asynchronously, the map - view calls these methods to notify your application when specific operations - complete. The map view also uses these methods to request information about - annotations displayed on the map, such as the styles and interaction modes to - apply to individual annotations. - */ -@protocol MGLMapViewDelegate <NSObject> - -@optional - -#pragma mark Responding to Map Viewpoint Changes - -/** - Tells the delegate that the viewpoint depicted by the map view is about to - change. - - This method is called whenever the currently displayed map camera will start - changing for any reason. - - @param mapView The map view whose viewpoint will change. - @param animated Whether the change will cause an animated effect on the map. - */ -- (void)mapView:(MGLMapView *)mapView cameraWillChangeAnimated:(BOOL)animated; - -/** - Tells the delegate that the viewpoint depicted by the map view is changing. - - This method is called as the currently displayed map camera changes as part of - an animation, whether due to a user gesture or due to a call to a method such - as `-[MGLMapView setCamera:animated:]`. This method can be called before - `-mapViewDidFinishLoadingMap:` is called. - - During the animation, this method may be called many times to report updates - to the viewpoint. Therefore, your implementation of this method should be as - lightweight as possible to avoid affecting performance. - - @param mapView The map view whose viewpoint is changing. - */ -- (void)mapViewCameraIsChanging:(MGLMapView *)mapView; - -/** - Tells the delegate that the viewpoint depicted by the map view has finished - changing. - - This method is called whenever the currently displayed map camera has finished - changing, after any calls to `-mapViewRegionIsChanging:` due to animation. - This method can be called before `-mapViewDidFinishLoadingMap:` is - called. - - @param mapView The map view whose viewpoint has changed. - @param animated Whether the change caused an animated effect on the map. - */ -- (void)mapView:(MGLMapView *)mapView cameraDidChangeAnimated:(BOOL)animated; - -/** - Asks the delegate whether the map view should be allowed to change from the - existing camera to the new camera in response to a user gesture. - - This method is called as soon as the user gesture is recognized. It is not - called in response to a programmatic camera change, such as by setting the - `centerCoordinate` property or calling `-flyToCamera:completionHandler:`. - - This method is called many times during gesturing, so you should avoid performing - complex or performance-intensive tasks in your implementation. - - @param mapView The map view that the user is manipulating. - @param oldCamera The camera representing the viewpoint at the moment the - gesture is recognized. If this method returns `NO`, the map view’s camera - continues to be this camera. - @param newCamera The expected camera after the gesture completes. If this - method returns `YES`, this camera becomes the map view’s camera. - @return A Boolean value indicating whether the map view should stay at - `oldCamera` or change to `newCamera`. - */ -- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera; - -#pragma mark Loading the Map - -/** - Tells the delegate that the map view will begin to load. - - This method is called whenever the map view starts loading, including when a - new style has been set and the map must reload. - - @param mapView The map view that is starting to load. - */ -- (void)mapViewWillStartLoadingMap:(MGLMapView *)mapView; - -/** - Tells the delegate that the map view has finished loading. - - This method is called whenever the map view finishes loading, either after the - initial load or after a style change has forced a reload. - - @param mapView The map view that has finished loading. - */ -- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView; - -/** - Tells the delegate that the map view was unable to load data needed for - displaying the map. - - This method may be called for a variety of reasons, including a network - connection failure or a failure to fetch the style from the server. You can use - the given error message to notify the user that map data is unavailable. - - @param mapView The map view that is unable to load the data. - @param error The reason the data could not be loaded. - */ -- (void)mapViewDidFailLoadingMap:(MGLMapView *)mapView withError:(NSError *)error; - -- (void)mapViewWillStartRenderingMap:(MGLMapView *)mapView; -- (void)mapViewDidFinishRenderingMap:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered; - -/** - Tells the delegate that the map view is about to redraw. - - This method is called any time the map view needs to redraw due to a change in - the viewpoint or style property transition. This method may be called very - frequently, even moreso than `-mapViewRegionIsChanging:`. Therefore, your - implementation of this method should be as lightweight as possible to avoid - affecting performance. - - @param mapView The map view that is about to redraw. - */ -- (void)mapViewWillStartRenderingFrame:(MGLMapView *)mapView; - -/** - Tells the delegate that the map view has just redrawn. - - This method is called any time the map view needs to redraw due to a change in - the viewpoint or style property transition. This method may be called very - frequently, even moreso than `-mapViewRegionIsChanging:`. Therefore, your - implementation of this method should be as lightweight as possible to avoid - affecting performance. - - @param mapView The map view that has just redrawn. - */ -- (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)mapViewDidBecomeIdle:(MGLMapView *)mapView; - -/** - Tells the delegate that the map has just finished loading a style. - - This method is called during the initialization of the map view and after any - subsequent loading of a new style. This method is called between the - `-mapViewWillStartRenderingMap:` and `-mapViewDidFinishRenderingMap:` delegate - methods. Changes to sources or layers of the current style do not cause this - method to be called. - - This method is the earliest opportunity to modify the layout or appearance of - the current style before the map view is displayed to the user. - - @param mapView The map view that has just loaded a style. - @param style The style that was loaded. - */ -- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style; - -- (nullable NSImage *)mapView:(MGLMapView *)mapView didFailToLoadImage:(NSString *)imageName; - -/** - Asks the delegate whether the map view should evict cached images. - - This method is called in two scenarios: when the cumulative size of unused images - exceeds the cache size or when the last tile that includes the image is removed from - memory. - - @param mapView The map view that is evicting the image. - @param imageName The image name that is going to be removed. - @return A Boolean value indicating whether the map view should evict - the cached image. - */ -- (BOOL)mapView:(MGLMapView *)mapView shouldRemoveStyleImage:(NSString *)imageName; - -#pragma mark Managing the Appearance of Annotations - -/** - Returns an annotation image object to mark the given point annotation object on - the map. - - @param mapView The map view that requested the annotation image. - @param annotation The object representing the annotation that is about to be - displayed. - @return The image object to display for the given annotation or `nil` if you - want to display the default marker image. - */ -- (nullable MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation; - -/** - Returns the alpha value to use when rendering a shape annotation. - - A value of 0.0 results in a completely transparent shape. A value of 1.0, the - default, results in a completely opaque shape. - - This method sets the opacity of an entire shape, inclusive of its stroke and - fill. To independently set the values for stroke or fill, specify an alpha - component in the color returned by `-mapView:strokeColorForShapeAnnotation:` or - `-mapView:fillColorForPolygonAnnotation:`. - - @param mapView The map view rendering the shape annotation. - @param annotation The annotation being rendered. - @return An alpha value between 0 and 1.0. - */ -- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation; - -/** - Returns the color to use when rendering the outline of a shape annotation. - - The default stroke color is the selected menu item color. If a pattern color is - specified, the result is undefined. - - Opacity may be set by specifying an alpha component. The default alpha value is - `1.0` and results in a completely opaque stroke. - - @param mapView The map view rendering the shape annotation. - @param annotation The annotation being rendered. - @return A color to use for the shape outline. - */ -- (NSColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation; - -/** - Returns the color to use when rendering the fill of a polygon annotation. - - The default fill color is the selected menu item color. If a pattern color is - specified, the result is undefined. - - Opacity may be set by specifying an alpha component. The default alpha value is - `1.0` and results in a completely opaque shape. - - @param mapView The map view rendering the polygon annotation. - @param annotation The annotation being rendered. - @return The polygon’s interior fill color. - */ -- (NSColor *)mapView:(MGLMapView *)mapView fillColorForPolygonAnnotation:(MGLPolygon *)annotation; - -/** - Returns the line width in points to use when rendering the outline of a - polyline annotation. - - By default, the polyline is outlined with a line 3.0 points wide. - - @param mapView The map view rendering the polygon annotation. - @param annotation The annotation being rendered. - @return A line width for the polyline, measured in points. - */ -- (CGFloat)mapView:(MGLMapView *)mapView lineWidthForPolylineAnnotation:(MGLPolyline *)annotation; - -#pragma mark Selecting Annotations - -/** - Returns a Boolean value indicating whether the shape annotation can be selected. - - If the return value is `YES`, the user can select the annotation by clicking - on it. If the delegate does not implement this method, the default value is `YES`. - - @param mapView The map view that has selected the annotation. - @param annotation The object representing the shape annotation. - @return A Boolean value indicating whether the annotation can be selected. - */ -- (BOOL)mapView:(MGLMapView *)mapView shapeAnnotationIsEnabled:(MGLShape *)annotation; - -/** - Tells the delegate that one of its annotations has been selected. - - You can use this method to track changes to the selection state of annotations. - - @param mapView The map view containing the annotation. - @param annotation The annotation that was selected. - */ -- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id <MGLAnnotation>)annotation; - -/** - Tells the delegate that one of its annotations has been deselected. - - You can use this method to track changes in the selection state of annotations. - - @param mapView The map view containing the annotation. - @param annotation The annotation that was deselected. - */ -- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id <MGLAnnotation>)annotation; - -#pragma mark Managing Callout Popovers - -/** - Returns a Boolean value indicating whether the annotation is able to display - extra information in a callout popover. - - This method is called after an annotation is selected, before any callout is - displayed for the annotation. - - If the return value is `YES`, a callout popover is shown when the user clicks - on an annotation, selecting it. The default callout displays the annotation’s - title and subtitle. You can customize the popover’s contents by implementing - the `-mapView:calloutViewControllerForAnnotation:` method. - - If the return value is `NO`, or if this method is absent from the delegate, or - if the annotation lacks a title, the annotation will not show a callout even - when selected. - - @param mapView The map view that has selected the annotation. - @param annotation The object representing the annotation. - @return A Boolean value indicating whether the annotation should show a - callout. - */ -- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation; - -/** - Returns a view controller to manage the callout popover’s content view. - - Like any instance of `NSPopover`, an annotation callout manages its contents - with a view controller. The annotation object is the view controller’s - represented object. This means that you can bind controls in the view - controller’s content view to KVO-compliant properties of the annotation object, - such as `title` and `subtitle`. - - If each annotation should have an identical callout, you can set the - `MGLMapView.calloutViewController` property instead. - - @param mapView The map view that is requesting a callout view controller. - @param annotation The object representing the annotation. - @return A view controller for the given annotation. - */ -- (nullable NSViewController *)mapView:(MGLMapView *)mapView calloutViewControllerForAnnotation:(id <MGLAnnotation>)annotation; - -@end - -NS_ASSUME_NONNULL_END diff --git a/platform/macos/src/MGLMapView_Private.h b/platform/macos/src/MGLMapView_Private.h deleted file mode 100644 index 3d9b36c30a..0000000000 --- a/platform/macos/src/MGLMapView_Private.h +++ /dev/null @@ -1,62 +0,0 @@ -#import "MGLMapView.h" - -#include <mbgl/util/size.hpp> - -namespace mbgl { - class Map; - class Renderer; -} - -@class MGLSource; - -@interface MGLMapView (Private) - -/// True if the view or application is in a state where it is not expected to be -/// actively drawing. -@property (nonatomic, readonly, getter=isDormant) BOOL dormant; - -// These properties exist because initially, both the latitude and longitude are -// NaN. You have to set both the latitude and longitude simultaneously. If you -// set the latitude but reuse the current longitude, and the current longitude -// happens to be NaN, there will be no change because the resulting coordinate -// pair is invalid. - -/// Center latitude set independently of the center longitude in an inspectable. -@property (nonatomic) CLLocationDegrees pendingLatitude; -/// Center longitude set independently of the center latitude in an inspectable. -@property (nonatomic) CLLocationDegrees pendingLongitude; - -/// The map view’s OpenGL rendering context, if it is backed by an OpenGL based view. -@property (readonly, nonatomic, nullable) CGLContextObj context; - -- (mbgl::Size)framebufferSize; - -/// Map observers -- (void)cameraWillChangeAnimated:(BOOL)animated; -- (void)cameraIsChanging; -- (void)cameraDidChangeAnimated:(BOOL)animated; -- (void)mapViewWillStartLoadingMap; -- (void)mapViewDidFinishLoadingMap; -- (void)mapViewDidFailLoadingMapWithError:(nonnull NSError *)error; -- (void)mapViewWillStartRenderingFrame; -- (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered; -- (void)mapViewWillStartRenderingMap; -- (void)mapViewDidFinishRenderingMapFullyRendered:(BOOL)fullyRendered; -- (void)mapViewDidBecomeIdle; -- (void)mapViewDidFinishLoadingStyle; -- (void)sourceDidChange:(nonnull MGLSource *)source; -- (BOOL)shouldRemoveStyleImage:(nonnull NSString *)imageName; - -/// Asynchronously render a frame of the map. -- (void)setNeedsRerender; - -/// Synchronously render a frame of the map. -- (void)renderSync; - -- (BOOL)isTargetingInterfaceBuilder; - -- (nonnull mbgl::Map *)mbglMap; - -- (nonnull mbgl::Renderer *)renderer; - -@end diff --git a/platform/macos/src/MGLOpenGLLayer.h b/platform/macos/src/MGLOpenGLLayer.h deleted file mode 100644 index 832664f397..0000000000 --- a/platform/macos/src/MGLOpenGLLayer.h +++ /dev/null @@ -1,10 +0,0 @@ -#import <Cocoa/Cocoa.h> - -NS_ASSUME_NONNULL_BEGIN - -/// A subclass of NSOpenGLLayer that creates the environment mbgl needs to -/// render good-looking maps. -@interface MGLOpenGLLayer : NSOpenGLLayer -@end - -NS_ASSUME_NONNULL_END diff --git a/platform/macos/src/MGLOpenGLLayer.mm b/platform/macos/src/MGLOpenGLLayer.mm deleted file mode 100644 index fde2b52404..0000000000 --- a/platform/macos/src/MGLOpenGLLayer.mm +++ /dev/null @@ -1,58 +0,0 @@ -#import "MGLOpenGLLayer.h" - -#import "MGLMapView_Private.h" - -@implementation MGLOpenGLLayer { - NSOpenGLContext *_context; -} - -- (MGLMapView *)mapView { - return (MGLMapView *)super.view; -} - -// - (BOOL)isAsynchronous { -// return YES; -// } - -- (BOOL)needsDisplayOnBoundsChange { - return YES; -} - -- (CGRect)frame { - return self.view.bounds; -} - -- (NSOpenGLContext *)openGLContextForPixelFormat:(NSOpenGLPixelFormat *)pixelFormat { - if (!_context) { - _context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil]; - } - return _context; -} - -- (NSOpenGLPixelFormat *)openGLPixelFormatForDisplayMask:(uint32_t)mask { - NSOpenGLPixelFormatAttribute pfas[] = { - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, - NSOpenGLPFAAccelerated, - NSOpenGLPFAClosestPolicy, - NSOpenGLPFAAccumSize, 32, - NSOpenGLPFAColorSize, 24, - NSOpenGLPFAAlphaSize, 8, - NSOpenGLPFADepthSize, 16, - NSOpenGLPFAStencilSize, 8, - NSOpenGLPFAScreenMask, mask, - NSOpenGLPFAAllowOfflineRenderers, // Allows using the integrated GPU - 0 - }; - return [[NSOpenGLPixelFormat alloc] initWithAttributes:pfas]; -} - -- (BOOL)canDrawInOpenGLContext:(__unused NSOpenGLContext *)context pixelFormat:(__unused NSOpenGLPixelFormat *)pixelFormat forLayerTime:(__unused CFTimeInterval)t displayTime:(__unused const CVTimeStamp *)ts { - return !self.mapView.dormant; -} - -- (void)drawInOpenGLContext:(NSOpenGLContext *)context pixelFormat:(NSOpenGLPixelFormat *)pixelFormat forLayerTime:(CFTimeInterval)t displayTime:(const CVTimeStamp *)ts { - [self.mapView renderSync]; - [super drawInOpenGLContext:context pixelFormat:pixelFormat forLayerTime:t displayTime:ts]; -} - -@end diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h deleted file mode 100644 index 6728992d6b..0000000000 --- a/platform/macos/src/Mapbox.h +++ /dev/null @@ -1,71 +0,0 @@ -#import <Cocoa/Cocoa.h> - -#import "MGLFoundation.h" - -/// Project version number for Mapbox. -FOUNDATION_EXPORT MGL_EXPORT double MapboxVersionNumber; - -/// Project version string for Mapbox. -FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; - -#import "MGLAccountManager.h" -#import "MGLAnnotation.h" -#import "MGLAnnotationImage.h" -#import "MGLClockDirectionFormatter.h" -#import "MGLCluster.h" -#import "MGLCompassDirectionFormatter.h" -#import "MGLCoordinateFormatter.h" -#import "MGLDistanceFormatter.h" -#import "MGLFeature.h" -#import "MGLGeometry.h" -#import "MGLLight.h" -#import "MGLMapCamera.h" -#import "MGLMapView.h" -#import "MGLMapView+IBAdditions.h" -#import "MGLMapViewDelegate.h" -#import "MGLMultiPoint.h" -#import "MGLOfflinePack.h" -#import "MGLOfflineRegion.h" -#import "MGLOfflineStorage.h" -#import "MGLOverlay.h" -#import "MGLPointAnnotation.h" -#import "MGLPointCollection.h" -#import "MGLPolygon.h" -#import "MGLPolyline.h" -#import "MGLShape.h" -#import "MGLShapeCollection.h" -#import "MGLStyle.h" -#import "MGLStyleLayer.h" -#import "MGLForegroundStyleLayer.h" -#import "MGLVectorStyleLayer.h" -#import "MGLFillStyleLayer.h" -#import "MGLFillExtrusionStyleLayer.h" -#import "MGLLineStyleLayer.h" -#import "MGLSymbolStyleLayer.h" -#import "MGLRasterStyleLayer.h" -#import "MGLCircleStyleLayer.h" -#import "MGLBackgroundStyleLayer.h" -#import "MGLHeatmapStyleLayer.h" -#import "MGLHillshadeStyleLayer.h" -#import "MGLOpenGLStyleLayer.h" -#import "MGLSource.h" -#import "MGLTileSource.h" -#import "MGLVectorTileSource.h" -#import "MGLShapeSource.h" -#import "MGLComputedShapeSource.h" -#import "MGLRasterTileSource.h" -#import "MGLRasterDEMSource.h" -#import "MGLImageSource.h" -#import "MGLTilePyramidOfflineRegion.h" -#import "MGLShapeOfflineRegion.h" -#import "MGLTypes.h" -#import "NSValue+MGLAdditions.h" -#import "MGLStyleValue.h" -#import "MGLAttributionInfo.h" -#import "MGLMapSnapshotter.h" -#import "NSExpression+MGLAdditions.h" -#import "NSPredicate+MGLAdditions.h" -#import "MGLLoggingConfiguration.h" -#import "MGLNetworkConfiguration.h" -#import "MGLAttributedExpression.h" -#import "MGLSDKMetricsManager.h" diff --git a/platform/macos/src/NSColor+MGLAdditions.h b/platform/macos/src/NSColor+MGLAdditions.h deleted file mode 100644 index a3c5aba63f..0000000000 --- a/platform/macos/src/NSColor+MGLAdditions.h +++ /dev/null @@ -1,28 +0,0 @@ -#import <Cocoa/Cocoa.h> - -#include <mbgl/util/color.hpp> -#include <mbgl/style/property_value.hpp> - -@interface NSColor (MGLAdditions) - -/** - Converts the color into an mbgl::Color in sRGB space. - */ -- (mbgl::Color)mgl_color; - -/** - Instantiates `NSColor` from an `mbgl::Color` - */ -+ (NSColor *)mgl_colorWithColor:(mbgl::Color)color; - -- (mbgl::style::PropertyValue<mbgl::Color>)mgl_colorPropertyValue; - -@end - -@interface NSExpression (MGLColorAdditions) - -+ (NSExpression *)mgl_expressionForRGBComponents:(NSArray<NSExpression *> *)components; -+ (NSExpression *)mgl_expressionForRGBAComponents:(NSArray<NSExpression *> *)components; -+ (NSColor *)mgl_colorWithRGBComponents:(NSArray<NSExpression *> *)componentExpressions; - -@end diff --git a/platform/macos/src/NSColor+MGLAdditions.mm b/platform/macos/src/NSColor+MGLAdditions.mm deleted file mode 100644 index 6df3d750ce..0000000000 --- a/platform/macos/src/NSColor+MGLAdditions.mm +++ /dev/null @@ -1,124 +0,0 @@ -#import "NSColor+MGLAdditions.h" - -@implementation NSColor (MGLAdditions) - -- (mbgl::Color)mgl_color { - CGFloat r, g, b, a; - - // The Mapbox Style Specification does not specify a color space, but it is - // assumed to be sRGB for consistency with CSS. - NSColor *srgbColor = self; - if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { - srgbColor = [srgbColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; - } else { - srgbColor = [srgbColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; - } - [srgbColor getRed:&r green:&g blue:&b alpha:&a]; - - // NSColor provides non-premultiplied color components, so we have to premultiply each - // color component with the alpha value to transform it into a valid - // mbgl::Color which expects premultiplied color components. - return { static_cast<float>(r*a), static_cast<float>(g*a), static_cast<float>(b*a), static_cast<float>(a) }; -} - -+ (NSColor *)mgl_colorWithColor:(mbgl::Color)color { - // If there is no alpha value, return original color values. - if (color.a == 0.0f) { - // macOS 10.12 Sierra and below uses calibrated RGB by default. - if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { - return [NSColor colorWithCalibratedRed:color.r green:color.g blue:color.b alpha:color.a]; - } else { - return [NSColor colorWithRed:color.r green:color.g blue:color.b alpha:color.a]; - } - } - - // mbgl::Color provides premultiplied color components, so we have to convert color - // components to non-premultiplied values to return a valid NSColor object. - float red = static_cast<float>((color.r / color.a)); - float green = static_cast<float>((color.g / color.a)); - float blue = static_cast<float>((color.b / color.a)); - - // macOS 10.12 Sierra and below uses calibrated RGB by default. - if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { - return [NSColor colorWithCalibratedRed:red green:green blue:blue alpha:color.a]; - } else { - return [NSColor colorWithRed:red green:green blue:blue alpha:color.a]; - } -} - -- (mbgl::style::PropertyValue<mbgl::Color>)mgl_colorPropertyValue { - mbgl::Color color = self.mgl_color; - return {{ color.r, color.g, color.b, color.a }}; -} - -@end - -@implementation NSExpression (MGLColorAdditions) - -+ (NSExpression *)mgl_expressionForRGBComponents:(NSArray<NSExpression *> *)components { - if (NSColor *color = [self mgl_colorWithRGBComponents:components]) { - return [NSExpression expressionForConstantValue:color]; - } - - NSExpression *color = [NSExpression expressionForConstantValue:[NSColor class]]; - NSExpression *alpha = [NSExpression expressionForConstantValue:@1.0]; - return [NSExpression expressionForFunction:color - selectorName:@"colorWithRed:green:blue:alpha:" - arguments:[components arrayByAddingObject:alpha]]; -} - -+ (NSExpression *)mgl_expressionForRGBAComponents:(NSArray<NSExpression *> *)components { - if (NSColor *color = [self mgl_colorWithRGBComponents:components]) { - return [NSExpression expressionForConstantValue:color]; - } - - NSExpression *color = [NSExpression expressionForConstantValue:[NSColor class]]; - return [NSExpression expressionForFunction:color - selectorName:@"colorWithRed:green:blue:alpha:" - arguments:components]; -} - -/** - Returns a color object corresponding to the given component expressions. - */ -+ (NSColor *)mgl_colorWithRGBComponents:(NSArray<NSExpression *> *)componentExpressions { - // Map the component expressions to constant components. If any component is - // a non-constant expression, the components cannot be converted into a - // constant color value. - std::vector<CGFloat> components; - for (NSExpression *componentExpression in componentExpressions) { - if (componentExpression.expressionType != NSConstantValueExpressionType) { - return nil; - } - - NSNumber *component = (NSNumber *)componentExpression.constantValue; - if (![component isKindOfClass:[NSNumber class]]) { - return nil; - } - - components.push_back(component.doubleValue / 255.0); - } - - if (components.size() < 4) { - components.push_back(1.0); - } else { - // Alpha - components.back() *= 255.0; - } - - - // macOS 10.12 Sierra and below uses calibrated RGB by default. - if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { - return [NSColor colorWithCalibratedRed:components[0] - green:components[1] - blue:components[2] - alpha:components[3]]; - } - // The Mapbox Style Specification does not specify a color space, but it is - // assumed to be sRGB for consistency with CSS. - return [NSColor colorWithColorSpace:[NSColorSpace sRGBColorSpace] - components:&components[0] - count:components.size()]; -} - -@end diff --git a/platform/macos/src/NSImage+MGLAdditions.h b/platform/macos/src/NSImage+MGLAdditions.h deleted file mode 100644 index c08fc57bea..0000000000 --- a/platform/macos/src/NSImage+MGLAdditions.h +++ /dev/null @@ -1,19 +0,0 @@ -#import <Cocoa/Cocoa.h> - -#include <mbgl/style/image.hpp> - -NS_ASSUME_NONNULL_BEGIN - -@interface NSImage (MGLAdditions) - -- (nullable instancetype)initWithMGLPremultipliedImage:(mbgl::PremultipliedImage&&)image; - -- (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)image; - -- (std::unique_ptr<mbgl::style::Image>)mgl_styleImageWithIdentifier:(NSString *)identifier; - -- (mbgl::PremultipliedImage) mgl_premultipliedImage; - -@end - -NS_ASSUME_NONNULL_END diff --git a/platform/macos/src/NSImage+MGLAdditions.mm b/platform/macos/src/NSImage+MGLAdditions.mm deleted file mode 100644 index 2666dfe790..0000000000 --- a/platform/macos/src/NSImage+MGLAdditions.mm +++ /dev/null @@ -1,49 +0,0 @@ -#import "NSImage+MGLAdditions.h" - -#include <mbgl/util/image+MGLAdditions.hpp> - -@implementation NSImage (MGLAdditions) - -- (nullable instancetype)initWithMGLPremultipliedImage:(mbgl::PremultipliedImage&&)src { - CGImageRef image = CGImageCreateWithMGLPremultipliedImage(std::move(src)); - if (!image) { - return nil; - } - - self = [self initWithCGImage:image size:NSZeroSize]; - CGImageRelease(image); - return self; -} - -- (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)styleImage { - CGImageRef image = CGImageCreateWithMGLPremultipliedImage(styleImage->getImage().clone()); - if (!image) { - return nil; - } - - NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:image]; - CGImageRelease(image); - CGFloat w = styleImage->getImage().size.width / styleImage->getPixelRatio(); - CGFloat h = styleImage->getImage().size.height / styleImage->getPixelRatio(); - if (self = [self initWithSize:NSMakeSize(w, h)]) { - [self addRepresentation:rep]; - [self setTemplate:styleImage->isSdf()]; - } - return self; -} - -- (std::unique_ptr<mbgl::style::Image>)mgl_styleImageWithIdentifier:(NSString *)identifier { - mbgl::PremultipliedImage cPremultipliedImage = self.mgl_premultipliedImage; - auto imageWidth = cPremultipliedImage.size.width; - return std::make_unique<mbgl::style::Image>([identifier UTF8String], - std::move(cPremultipliedImage), - (float)(imageWidth / self.size.width), - [self isTemplate]); -} - -- (mbgl::PremultipliedImage)mgl_premultipliedImage { - CGImageRef ref = [self CGImageForProposedRect:nullptr context:nullptr hints:nullptr]; - return MGLPremultipliedImageFromCGImage(ref); -} - -@end diff --git a/platform/macos/src/NSProcessInfo+MGLAdditions.h b/platform/macos/src/NSProcessInfo+MGLAdditions.h deleted file mode 100644 index 1dc1439d53..0000000000 --- a/platform/macos/src/NSProcessInfo+MGLAdditions.h +++ /dev/null @@ -1,11 +0,0 @@ -#import <Foundation/Foundation.h> - -@interface NSProcessInfo (MGLAdditions) - -/** - Returns YES if the current process is Interface Builder’s helper process for - rendering designables. - */ -- (BOOL)mgl_isInterfaceBuilderDesignablesAgent; - -@end diff --git a/platform/macos/src/NSProcessInfo+MGLAdditions.m b/platform/macos/src/NSProcessInfo+MGLAdditions.m deleted file mode 100644 index 0c287c030b..0000000000 --- a/platform/macos/src/NSProcessInfo+MGLAdditions.m +++ /dev/null @@ -1,10 +0,0 @@ -#import "NSProcessInfo+MGLAdditions.h" - -@implementation NSProcessInfo (MGLAdditions) - -- (BOOL)mgl_isInterfaceBuilderDesignablesAgent { - NSString *processName = self.processName; - return [processName hasPrefix:@"IBAgent"] || [processName hasPrefix:@"IBDesignablesAgent"]; -} - -@end |