summaryrefslogtreecommitdiff
path: root/platform/macos/src
diff options
context:
space:
mode:
Diffstat (limited to 'platform/macos/src')
-rw-r--r--platform/macos/src/MGLMapView.mm336
-rw-r--r--platform/macos/src/MGLMapViewDelegate.h35
-rw-r--r--platform/macos/src/Mapbox.h11
-rw-r--r--platform/macos/src/NSColor+MGLAdditions.h6
-rw-r--r--platform/macos/src/NSColor+MGLAdditions.mm8
-rw-r--r--platform/macos/src/NSImage+MGLAdditions.h9
-rw-r--r--platform/macos/src/NSImage+MGLAdditions.mm22
7 files changed, 240 insertions, 187 deletions
diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm
index 8fca7b86ff..a8f91a24e7 100644
--- a/platform/macos/src/MGLMapView.mm
+++ b/platform/macos/src/MGLMapView.mm
@@ -30,6 +30,7 @@
#import <mbgl/math/wrap.hpp>
#import <mbgl/util/constants.hpp>
#import <mbgl/util/chrono.hpp>
+#import <mbgl/util/run_loop.hpp>
#import <unordered_map>
#import <unordered_set>
@@ -41,6 +42,7 @@
#import "NSString+MGLAdditions.h"
#import "NSURL+MGLAdditions.h"
#import "NSColor+MGLAdditions.h"
+#import "NSImage+MGLAdditions.h"
#import <QuartzCore/QuartzCore.h>
@@ -112,6 +114,11 @@ NSImage *MGLDefaultMarkerImage() {
return [[NSImage alloc] initWithContentsOfFile:path];
}
+/// Initializes the run loop shim that lives on the main thread.
+void MGLinitializeRunLoop() {
+ static mbgl::util::RunLoop mainRunLoop;
+}
+
/// Converts a media timing function into a unit bezier object usable in mbgl.
mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function) {
if (!function) {
@@ -151,7 +158,7 @@ public:
/// Cross-platform map view controller.
mbgl::Map *_mbglMap;
MGLMapViewImpl *_mbglView;
-
+
NSPanGestureRecognizer *_panGestureRecognizer;
NSMagnificationGestureRecognizer *_magnificationGestureRecognizer;
NSRotationGestureRecognizer *_rotationGestureRecognizer;
@@ -159,7 +166,7 @@ public:
CLLocationDirection _directionAtBeginningOfGesture;
CGFloat _pitchAtBeginningOfGesture;
BOOL _didHideCursorDuringGesture;
-
+
MGLAnnotationContextMap _annotationContextsByAnnotationTag;
MGLAnnotationTag _selectedAnnotationTag;
MGLAnnotationTag _lastSelectedAnnotationTag;
@@ -170,22 +177,22 @@ public:
BOOL _wantsToolTipRects;
/// True if any annotation images that have custom cursors have been installed.
BOOL _wantsCursorRects;
-
+
// 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;
}
@@ -225,7 +232,7 @@ public:
- (void)awakeFromNib {
[super awakeFromNib];
-
+
self.styleURL = nil;
}
@@ -234,11 +241,13 @@ public:
}
- (void)commonInit {
+ MGLinitializeRunLoop();
+
_isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent;
-
+
// Set up cross-platform controllers and resources.
_mbglView = new MGLMapViewImpl(self, [NSScreen mainScreen].backingScaleFactor);
-
+
// Delete the pre-offline ambient cache at
// ~/Library/Caches/com.mapbox.sdk.ios/cache.db.
NSURL *cachesDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
@@ -250,36 +259,36 @@ public:
[NSBundle mgl_frameworkBundle].bundleIdentifier];
NSURL *legacyCacheURL = [cachesDirectoryURL URLByAppendingPathComponent:@"cache.db"];
[[NSFileManager defaultManager] removeItemAtURL:legacyCacheURL error:NULL];
-
+
mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource;
_mbglMap = new mbgl::Map(*_mbglView, *mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None, mbgl::ViewportMode::Default);
[self validateTileCacheSize];
-
+
// 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.
self.layer = _isTargetingInterfaceBuilder ? [CALayer layer] : [MGLOpenGLLayer layer];
-
+
// Notify map object when network reachability status changes.
MGLReachability *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 = {};
_selectedAnnotationTag = MGLAnnotationTagNotFound;
_lastSelectedAnnotationTag = MGLAnnotationTagNotFound;
_annotationsNearbyLastClick = {};
-
+
// Jump to Null Island initially.
self.automaticallyAdjustsContentInsets = YES;
mbgl::CameraOptions options;
@@ -345,26 +354,26 @@ public:
- (void)installAttributionView {
_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];
@@ -376,27 +385,27 @@ public:
_zoomEnabled = YES;
_rotateEnabled = YES;
_pitchEnabled = YES;
-
+
_panGestureRecognizer = [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
_panGestureRecognizer.delaysKeyEvents = YES;
[self addGestureRecognizer:_panGestureRecognizer];
-
+
NSClickGestureRecognizer *clickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleClickGesture:)];
clickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO;
[self addGestureRecognizer:clickGestureRecognizer];
-
+
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];
}
@@ -405,7 +414,7 @@ public:
/// hard-coded to the standard Mapbox and OpenStreetMap attribution.
- (void)updateAttributionView {
self.attributionView.subviews = @[];
-
+
for (NSUInteger i = 0; i < sizeof(MGLAttributions) / sizeof(MGLAttributions[0]); i++) {
// For each attribution, add a borderless button that responds to clicks
// and feels like a hyperlink.
@@ -413,7 +422,7 @@ public:
NSButton *button = [[MGLAttributionButton alloc] initWithTitle:MGLAttributions[i].title URL:url];
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.
@@ -441,14 +450,14 @@ public:
- (void)dealloc {
[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;
@@ -510,14 +519,14 @@ public:
- (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:)]) {
@@ -549,7 +558,7 @@ public:
if (_isTargetingInterfaceBuilder) {
return;
}
-
+
// Default to Streets.
if (!styleURL) {
// An access token is required to load any default style, including
@@ -559,7 +568,7 @@ public:
}
styleURL = [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion];
}
-
+
styleURL = styleURL.mgl_URLByStandardizingScheme;
_mbglMap->setStyleURL(styleURL.absoluteString.UTF8String);
}
@@ -577,7 +586,7 @@ public:
if (!self.dormant && !newWindow) {
self.dormant = YES;
}
-
+
[self.window removeObserver:self forKeyPath:@"contentLayoutRect"];
[self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"];
}
@@ -587,11 +596,11 @@ public:
if (self.dormant && window) {
self.dormant = NO;
}
-
+
if (window && _mbglMap->getConstrainMode() == mbgl::ConstrainMode::None) {
_mbglMap->setConstrainMode(mbgl::ConstrainMode::HeightOnly);
}
-
+
[window addObserver:self
forKeyPath:@"contentLayoutRect"
options:NSKeyValueObservingOptionInitial
@@ -640,7 +649,7 @@ public:
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:
@@ -659,7 +668,7 @@ public:
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:
@@ -678,7 +687,7 @@ public:
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
@@ -709,7 +718,7 @@ public:
attribute:NSLayoutAttributeTrailing
multiplier:1
constant:8]];
-
+
[super updateConstraints];
}
@@ -747,20 +756,20 @@ public:
if (!_mbglMap) {
return;
}
-
+
CGFloat zoomFactor = self.maximumZoomLevel - self.minimumZoomLevel + 1;
CGFloat cpuFactor = [NSProcessInfo processInfo].processorCount;
CGFloat memoryFactor = (CGFloat)[NSProcessInfo processInfo].physicalMemory / 1000 / 1000 / 1000;
CGFloat sizeFactor = (NSWidth(self.bounds) / mbgl::util::tileSize) * (NSHeight(self.bounds) / mbgl::util::tileSize);
-
+
NSUInteger cacheSize = zoomFactor * cpuFactor * memoryFactor * sizeFactor * 0.5;
-
+
_mbglMap->setSourceTileCacheSize(cacheSize);
}
- (void)invalidate {
MGLAssertIsMainThread();
-
+
[self.layer setNeedsDisplay];
}
@@ -769,7 +778,7 @@ public:
if (!_mbglMap) {
return;
}
-
+
switch (change) {
case mbgl::MapChangeRegionWillChange:
case mbgl::MapChangeRegionWillChangeAnimated:
@@ -786,7 +795,7 @@ public:
// while animating.
[self updateCompass];
[self updateAnnotationCallouts];
-
+
if ([self.delegate respondsToSelector:@selector(mapViewCameraIsChanging:)]) {
[self.delegate mapViewCameraIsChanging:self];
}
@@ -804,7 +813,7 @@ public:
[self updateCompass];
[self updateAnnotationCallouts];
[self updateAnnotationTrackingAreas];
-
+
if ([self.delegate respondsToSelector:@selector(mapView:cameraDidChangeAnimated:)]) {
BOOL animated = change == mbgl::MapChangeRegionDidChangeAnimated;
[self.delegate mapView:self cameraDidChangeAnimated:animated];
@@ -865,6 +874,14 @@ public:
}
break;
}
+ case mbgl::MapChangeDidFinishLoadingStyle:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapView:didFinishLoadingStyle:)])
+ {
+ [self.delegate mapView:self didFinishLoadingStyle:self.style];
+ }
+ break;
+ }
}
}
@@ -878,7 +895,7 @@ public:
- (void)printWithImage:(NSImage *)image {
NSImageView *imageView = [[NSImageView alloc] initWithFrame:self.bounds];
imageView.image = image;
-
+
NSPrintOperation *op = [NSPrintOperation printOperationWithView:imageView];
[op runOperation];
}
@@ -1045,7 +1062,7 @@ public:
if ([self.camera isEqual:camera]) {
return;
}
-
+
mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera];
mbgl::AnimationOptions animationOptions;
if (duration > 0) {
@@ -1062,7 +1079,7 @@ public:
});
};
}
-
+
[self willChangeValueForKey:@"camera"];
_mbglMap->easeTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"camera"];
@@ -1081,7 +1098,7 @@ public:
if ([self.camera isEqual:camera]) {
return;
}
-
+
mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera];
mbgl::AnimationOptions animationOptions;
if (duration >= 0) {
@@ -1103,7 +1120,7 @@ public:
});
};
}
-
+
[self willChangeValueForKey:@"camera"];
_mbglMap->flyTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"camera"];
@@ -1145,7 +1162,7 @@ public:
- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated {
_mbglMap->cancelTransitions();
-
+
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets);
mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding);
@@ -1153,7 +1170,7 @@ public:
if (animated) {
animationOptions.duration = MGLDurationInSeconds(MGLAnimationDuration);
}
-
+
[self willChangeValueForKey:@"visibleCoordinateBounds"];
animationOptions.transitionFinishFn = ^() {
[self didChangeValueForKey:@"visibleCoordinateBounds"];
@@ -1196,7 +1213,7 @@ public:
if (!_automaticallyAdjustsContentInsets) {
return;
}
-
+
NSEdgeInsets contentInsets = self.contentInsets;
if ((self.window.styleMask & NSFullSizeContentViewWindowMask)
&& !self.window.titlebarAppearsTransparent) {
@@ -1210,7 +1227,7 @@ public:
} else {
contentInsets = NSEdgeInsetsZero;
}
-
+
self.contentInsets = contentInsets;
}
@@ -1222,7 +1239,7 @@ public:
if (NSEdgeInsetsEqual(contentInsets, self.contentInsets)) {
return;
}
-
+
// 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.
@@ -1243,12 +1260,12 @@ public:
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);
@@ -1258,7 +1275,7 @@ public:
|| 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
@@ -1270,15 +1287,15 @@ public:
CGDisplayShowCursor(kCGDirectMainDisplay);
}
}
-
+
if (flags & NSShiftKeyMask) {
// Shift-drag to zoom.
if (!self.zoomEnabled) {
return;
}
-
+
_mbglMap->cancelTransitions();
-
+
if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
_scaleAtBeginningOfGesture = _mbglMap->getScale();
} else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
@@ -1288,7 +1305,7 @@ public:
} else if (flags & NSAlternateKeyMask) {
// Option-drag to rotate and/or tilt.
_mbglMap->cancelTransitions();
-
+
if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
_directionAtBeginningOfGesture = self.direction;
_pitchAtBeginningOfGesture = _mbglMap->getPitch();
@@ -1307,7 +1324,7 @@ public:
} else if (self.scrollEnabled) {
// Otherwise, drag to pan.
_mbglMap->cancelTransitions();
-
+
if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
delta.y *= -1;
[self offsetCenterCoordinateBy:delta animated:NO];
@@ -1329,9 +1346,9 @@ public:
if (!self.zoomEnabled) {
return;
}
-
+
_mbglMap->cancelTransitions();
-
+
if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
_mbglMap->setGestureInProgress(true);
_scaleAtBeginningOfGesture = _mbglMap->getScale();
@@ -1357,7 +1374,7 @@ public:
|| [self subviewContainingGesture:gestureRecognizer]) {
return;
}
-
+
NSPoint gesturePoint = [gestureRecognizer locationInView:self];
MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:gesturePoint persistingResults:YES];
if (hitAnnotationTag != MGLAnnotationTagNotFound) {
@@ -1385,9 +1402,9 @@ public:
|| [self subviewContainingGesture:gestureRecognizer]) {
return;
}
-
+
_mbglMap->cancelTransitions();
-
+
NSPoint gesturePoint = [gestureRecognizer locationInView:self];
[self scaleBy:2 atPoint:gesturePoint animated:YES];
}
@@ -1396,9 +1413,9 @@ public:
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 scaleBy:0.5 atPoint:gesturePoint animated:YES];
@@ -1409,9 +1426,9 @@ public:
if (!self.rotateEnabled) {
return;
}
-
+
_mbglMap->cancelTransitions();
-
+
if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
_mbglMap->setGestureInProgress(true);
_directionAtBeginningOfGesture = self.direction;
@@ -1455,13 +1472,13 @@ public:
&& _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];
@@ -1573,7 +1590,7 @@ public:
if (_annotationContextsByAnnotationTag.empty()) {
return nil;
}
-
+
// Map all the annotation tags to the annotations themselves.
std::vector<id <MGLAnnotation>> annotations;
std::transform(_annotationContextsByAnnotationTag.begin(),
@@ -1590,7 +1607,7 @@ public:
if (!_annotationContextsByAnnotationTag.count(tag)) {
return nil;
}
-
+
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag[tag];
return annotationContext.annotation;
}
@@ -1600,7 +1617,7 @@ public:
if (!annotation) {
return MGLAnnotationTagNotFound;
}
-
+
for (auto &pair : _annotationContextsByAnnotationTag) {
if (pair.second.annotation == annotation) {
return pair.first;
@@ -1619,21 +1636,21 @@ public:
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");
-
+
if ([annotation isKindOfClass:[MGLMultiPoint class]]) {
// Actual multipoints aren’t supported as annotations.
if ([annotation isMemberOfClass:[MGLMultiPoint class]]
|| [annotation isMemberOfClass:[MGLMultiPointFeature class]]) {
continue;
}
-
+
// The multipoint knows how to style itself (with the map view’s help).
MGLMultiPoint *multiPoint = (MGLMultiPoint *)annotation;
if (!multiPoint.pointCount) {
@@ -1659,13 +1676,13 @@ public:
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];
@@ -1680,7 +1697,7 @@ public:
context.annotation = annotation;
context.imageReuseIdentifier = annotationImage.reuseIdentifier;
_annotationContextsByAnnotationTag[annotationTag] = context;
-
+
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];
@@ -1694,7 +1711,7 @@ public:
}
[self didChangeValueForKey:@"annotations"];
-
+
[self updateAnnotationTrackingAreas];
}
@@ -1716,36 +1733,23 @@ public:
- (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;
}
-
- // Create a bitmap image representation from the image, respecting backing
- // scale factor and any resizing done on the image at runtime.
- // http://www.cocoabuilder.com/archive/cocoa/82430-nsimage-getting-raw-bitmap-data.html#82431
- [image lockFocus];
- NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:{ NSZeroPoint, size }];
- [image unlockFocus];
-
- // Get the image’s raw pixel data as an RGBA buffer.
- std::string pixelString((const char *)rep.bitmapData, rep.pixelsWide * rep.pixelsHigh * 4 /* RGBA */);
-
- mbgl::PremultipliedImage cPremultipliedImage(rep.pixelsWide, rep.pixelsHigh);
- std::copy(rep.bitmapData, rep.bitmapData + cPremultipliedImage.size(), cPremultipliedImage.data.get());
- auto cSpriteImage = std::make_shared<mbgl::SpriteImage>(std::move(cPremultipliedImage),
- (float)(rep.pixelsWide / size.width));
- _mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, cSpriteImage);
-
+
+ std::shared_ptr<mbgl::SpriteImage> sprite(annotationImage.image.mgl_spriteImage);
+ _mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, sprite);
+
// 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;
@@ -1762,12 +1766,12 @@ public:
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);
@@ -1777,9 +1781,9 @@ public:
if (annotationTag == _lastSelectedAnnotationTag) {
_lastSelectedAnnotationTag = MGLAnnotationTagNotFound;
}
-
+
_annotationContextsByAnnotationTag.erase(annotationTag);
-
+
if ([annotation isKindOfClass:[NSObject class]] &&
![annotation isKindOfClass:[MGLMultiPoint class]]) {
[(NSObject *)annotation removeObserver:self forKeyPath:@"coordinate" context:(void *)(NSUInteger)annotationTag];
@@ -1789,9 +1793,9 @@ public:
_mbglMap->removeAnnotation(annotationTag);
}
-
+
[self didChangeValueForKey:@"annotations"];
-
+
[self updateAnnotationTrackingAreas];
}
@@ -1805,11 +1809,11 @@ public:
/**
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
@@ -1825,13 +1829,13 @@ public:
queryRect = NSInsetRect(queryRect, -MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);
std::vector<MGLAnnotationTag> nearbyAnnotations = [self annotationTagsInRect:queryRect];
-
+
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) {
@@ -1840,12 +1844,12 @@ public:
if (!annotation) {
return true;
}
-
+
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
@@ -1855,13 +1859,13 @@ public:
});
nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end));
}
-
+
MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound;
if (nearbyAnnotations.size()) {
// The annotation tags need to be stable in order to compare them with
// the remembered tags.
std::sort(nearbyAnnotations.begin(), nearbyAnnotations.end());
-
+
if (nearbyAnnotations == _annotationsNearbyLastClick) {
// The first selection in the cycle should be the one nearest to the
// click.
@@ -1875,7 +1879,7 @@ public:
coordinateB.longitude - currentCoordinate.longitude);
return distanceA < distanceB;
});
-
+
// 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
@@ -1904,14 +1908,14 @@ public:
if (persist) {
_annotationsNearbyLastClick = nearbyAnnotations;
}
-
+
// Choose the first nearby annotation.
if (nearbyAnnotations.size()) {
hitAnnotationTag = nearbyAnnotations.front();
}
}
}
-
+
return hitAnnotationTag;
}
@@ -1950,13 +1954,13 @@ public:
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;
}
-
+
// Select the annotation if it’s visible.
if (MGLCoordinateInCoordinateBounds(firstAnnotation.coordinate, self.visibleCoordinateBounds)) {
[self selectAnnotation:firstAnnotation];
@@ -1969,29 +1973,29 @@ public:
if (!annotation || [annotation isKindOfClass:[MGLMultiPoint class]]) {
return;
}
-
+
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];
}
-
+
// The annotation can’t be selected if no part of it is hittable.
NSRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag];
if (NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) {
return;
}
-
+
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.
@@ -2001,7 +2005,7 @@ public:
&& [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)]
&& [self.delegate mapView:self annotationCanShowCallout:annotation]) {
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;
@@ -2049,7 +2053,7 @@ public:
- (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
@@ -2066,7 +2070,7 @@ public:
// annotation.
viewController.representedObject = annotation;
callout.contentViewController = viewController;
-
+
return callout;
}
@@ -2094,7 +2098,7 @@ public:
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,
@@ -2114,10 +2118,10 @@ public:
|| _annotationContextsByAnnotationTag.count(annotationTag) == 0) {
return nil;
}
-
+
NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier;
NSString *symbolName = customSymbol.length ? customSymbol : MGLDefaultStyleMarkerSymbolName;
-
+
return [self dequeueReusableAnnotationImageWithIdentifier:symbolName];
}
@@ -2125,11 +2129,11 @@ public:
if (!annotation || self.selectedAnnotation != annotation) {
return;
}
-
+
// Close the callout popover gracefully.
NSPopover *callout = self.calloutForSelectedAnnotation;
[callout performClose:self];
-
+
self.selectedAnnotation = nil;
}
@@ -2159,8 +2163,18 @@ public:
#pragma mark MGLMultiPointDelegate methods
- (double)alphaForShapeAnnotation:(MGLShape *)annotation {
+ // The explicit -mapView:alphaForShapeAnnotation: delegate method is deprecated
+ // but still used, if implemented. When not implemented, call the stroke or
+ // fill color delegate methods and pull the alpha from the returned color.
if (_delegateHasAlphasForShapeAnnotations) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return [self.delegate mapView:self alphaForShapeAnnotation:annotation];
+#pragma clang diagnostic pop
+ } else if ([annotation isKindOfClass:[MGLPolygon class]]) {
+ return [self fillColorForPolygonAnnotation:(MGLPolygon *)annotation].a ?: 1.0;
+ } else if ([annotation isKindOfClass:[MGLShape class]]) {
+ return [self strokeColorForShapeAnnotation:annotation].a ?: 1.0;
}
return 1.0;
}
@@ -2169,14 +2183,14 @@ public:
NSColor *color = (_delegateHasStrokeColorsForShapeAnnotations
? [self.delegate mapView:self strokeColorForShapeAnnotation:annotation]
: [NSColor selectedMenuItemColor]);
- return color.mbgl_color;
+ return color.mgl_color;
}
- (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation {
NSColor *color = (_delegateHasFillColorsForShapeAnnotations
? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation]
: [NSColor selectedMenuItemColor]);
- return color.mbgl_color;
+ return color.mgl_color;
}
- (CGFloat)lineWidthForPolylineAnnotation:(MGLPolyline *)annotation {
@@ -2201,7 +2215,7 @@ public:
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];
}
@@ -2258,7 +2272,7 @@ public:
}
}
}
-
+
// Blow away any cursor tracking areas and rebuild them. That’s the
// potentially expensive part.
if (_wantsCursorRects) {
@@ -2279,13 +2293,13 @@ public:
[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];
@@ -2311,7 +2325,7 @@ public:
- (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesAtPoint:(NSPoint)point inStyleLayersWithIdentifiers:(NS_SET_OF(NSString *) *)styleLayerIdentifiers {
// 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;
@@ -2321,7 +2335,7 @@ public:
}];
optionalLayerIDs = layerIDs;
}
-
+
std::vector<mbgl::Feature> features = _mbglMap->queryRenderedFeatures(screenCoordinate, optionalLayerIDs);
return MGLFeaturesFromMBGLFeatures(features);
}
@@ -2336,7 +2350,7 @@ public:
{ 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;
@@ -2346,7 +2360,7 @@ public:
}];
optionalLayerIDs = layerIDs;
}
-
+
std::vector<mbgl::Feature> features = _mbglMap->queryRenderedFeatures(screenBox, optionalLayerIDs);
return MGLFeaturesFromMBGLFeatures(features);
}
@@ -2355,7 +2369,7 @@ public:
- (void)prepareForInterfaceBuilder {
[super prepareForInterfaceBuilder];
-
+
// Color the background a glorious Mapbox teal.
self.layer.borderColor = [NSColor colorWithRed:59/255.
green:178/255.
@@ -2366,7 +2380,7 @@ public:
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;
@@ -2425,7 +2439,7 @@ public:
bounds.extend([self convertPoint:{ NSMaxX(rect), NSMinY(rect) } toLatLngFromView:view]);
bounds.extend([self convertPoint:{ NSMaxX(rect), NSMaxY(rect) } toLatLngFromView:view]);
bounds.extend([self convertPoint:{ NSMinX(rect), NSMaxY(rect) } toLatLngFromView:view]);
-
+
// The world is wrapping if a point just outside the bounds is also within
// the rect.
mbgl::LatLng outsideLatLng;
@@ -2440,13 +2454,13 @@ public:
bounds.east() + 1,
};
}
-
+
// If the world is wrapping, extend the bounds to cover all longitudes.
if (NSPointInRect([self convertLatLng:outsideLatLng toPointToView:view], rect)) {
bounds.extend(mbgl::LatLng(bounds.south(), -180));
bounds.extend(mbgl::LatLng(bounds.south(), 180));
}
-
+
return bounds;
}
@@ -2554,7 +2568,7 @@ public:
mbgl::PremultipliedImage image { size[0], size[1] };
MBGL_CHECK_ERROR(glReadPixels(0, 0, size[0], size[1], GL_RGBA, GL_UNSIGNED_BYTE, image.data.get()));
-
+
const size_t stride = image.stride();
auto tmp = std::make_unique<uint8_t[]>(stride);
uint8_t *rgba = image.data.get();
@@ -2563,14 +2577,14 @@ public:
std::memcpy(rgba + i * stride, rgba + j * stride, stride);
std::memcpy(rgba + j * stride, tmp.get(), stride);
}
-
+
return image;
}
-
+
private:
/// Cocoa map view that this adapter bridges to.
__weak MGLMapView *nativeView = nullptr;
-
+
/// Backing scale factor of the view.
const float scaleFactor;
};
diff --git a/platform/macos/src/MGLMapViewDelegate.h b/platform/macos/src/MGLMapViewDelegate.h
index 8f4922d304..c5af93f8ad 100644
--- a/platform/macos/src/MGLMapViewDelegate.h
+++ b/platform/macos/src/MGLMapViewDelegate.h
@@ -125,6 +125,23 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered;
+/**
+ 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;
+
#pragma mark Managing the Appearance of Annotations
/**
@@ -139,17 +156,7 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (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.
-
- @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;
+- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation __attribute__((deprecated("Use -mapView:strokeColorForShapeAnnotation: or -mapView:fillColorForPolygonAnnotation:.")));
/**
Returns the color to use when rendering the outline of a shape annotation.
@@ -157,6 +164,9 @@ NS_ASSUME_NONNULL_BEGIN
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.
@@ -169,6 +179,9 @@ NS_ASSUME_NONNULL_BEGIN
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.
diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h
index 0217bca44a..67e3775100 100644
--- a/platform/macos/src/Mapbox.h
+++ b/platform/macos/src/Mapbox.h
@@ -30,7 +30,8 @@ FOUNDATION_EXPORT const unsigned char MapboxVersionString[];
#import "MGLShapeCollection.h"
#import "MGLStyle.h"
#import "MGLStyleLayer.h"
-#import "MGLBaseStyleLayer.h"
+#import "MGLForegroundStyleLayer.h"
+#import "MGLVectorStyleLayer.h"
#import "MGLFillStyleLayer.h"
#import "MGLLineStyleLayer.h"
#import "MGLSymbolStyleLayer.h"
@@ -44,10 +45,4 @@ FOUNDATION_EXPORT const unsigned char MapboxVersionString[];
#import "MGLTilePyramidOfflineRegion.h"
#import "MGLTypes.h"
#import "NSValue+MGLAdditions.h"
-#import "MGLStyleAttributeValue.h"
-#import "MGLStyleAttributeFunction.h"
-#import "NSColor+MGLStyleAttributeAdditions.h"
-#import "NSNumber+MGLStyleAttributeAdditions.h"
-#import "NSValue+MGLStyleAttributeAdditions.h"
-#import "NSString+MGLStyleAttributeAdditions.h"
-#import "NSArray+MGLStyleAttributeAdditions.h"
+#import "MGLStyleValue.h"
diff --git a/platform/macos/src/NSColor+MGLAdditions.h b/platform/macos/src/NSColor+MGLAdditions.h
index 2ba38f7d90..8dd8c1c17c 100644
--- a/platform/macos/src/NSColor+MGLAdditions.h
+++ b/platform/macos/src/NSColor+MGLAdditions.h
@@ -8,13 +8,13 @@
/**
Converts the color into an mbgl::Color in calibrated RGB space.
*/
-- (mbgl::Color)mbgl_color;
+- (mbgl::Color)mgl_color;
/**
Instantiates `NSColor` from an `mbgl::Color`
*/
-+ (NSColor *)mbgl_colorWithColor:(mbgl::Color)color;
++ (NSColor *)mgl_colorWithColor:(mbgl::Color)color;
-- (mbgl::style::PropertyValue<mbgl::Color>)mbgl_colorPropertyValue;
+- (mbgl::style::PropertyValue<mbgl::Color>)mgl_colorPropertyValue;
@end
diff --git a/platform/macos/src/NSColor+MGLAdditions.mm b/platform/macos/src/NSColor+MGLAdditions.mm
index a75dc488fd..e347fd1798 100644
--- a/platform/macos/src/NSColor+MGLAdditions.mm
+++ b/platform/macos/src/NSColor+MGLAdditions.mm
@@ -2,7 +2,7 @@
@implementation NSColor (MGLAdditions)
-- (mbgl::Color)mbgl_color
+- (mbgl::Color)mgl_color
{
CGFloat r, g, b, a;
@@ -11,14 +11,14 @@
return { (float)r, (float)g, (float)b, (float)a };
}
-+ (NSColor *)mbgl_colorWithColor:(mbgl::Color)color
++ (NSColor *)mgl_colorWithColor:(mbgl::Color)color
{
return [NSColor colorWithRed:color.r green:color.g blue:color.b alpha:color.a];
}
-- (mbgl::style::PropertyValue<mbgl::Color>)mbgl_colorPropertyValue
+- (mbgl::style::PropertyValue<mbgl::Color>)mgl_colorPropertyValue
{
- mbgl::Color color = self.mbgl_color;
+ mbgl::Color color = self.mgl_color;
return {{ color.r, color.g, color.b, color.a }};
}
diff --git a/platform/macos/src/NSImage+MGLAdditions.h b/platform/macos/src/NSImage+MGLAdditions.h
new file mode 100644
index 0000000000..a2144e96a2
--- /dev/null
+++ b/platform/macos/src/NSImage+MGLAdditions.h
@@ -0,0 +1,9 @@
+#import <Cocoa/Cocoa.h>
+
+#include <mbgl/sprite/sprite_image.hpp>
+
+@interface NSImage (MGLAdditions)
+
+- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage;
+
+@end
diff --git a/platform/macos/src/NSImage+MGLAdditions.mm b/platform/macos/src/NSImage+MGLAdditions.mm
new file mode 100644
index 0000000000..7d02271bb3
--- /dev/null
+++ b/platform/macos/src/NSImage+MGLAdditions.mm
@@ -0,0 +1,22 @@
+#import "NSImage+MGLAdditions.h"
+
+@implementation NSImage (MGLAdditions)
+
+- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage {
+ // Create a bitmap image representation from the image, respecting backing
+ // scale factor and any resizing done on the image at runtime.
+ // http://www.cocoabuilder.com/archive/cocoa/82430-nsimage-getting-raw-bitmap-data.html#82431
+ [self lockFocus];
+ NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:{ NSZeroPoint, self.size }];
+ [self unlockFocus];
+
+ // Get the image’s raw pixel data as an RGBA buffer.
+ std::string pixelString((const char *)rep.bitmapData, rep.pixelsWide * rep.pixelsHigh * 4 /* RGBA */);
+
+ mbgl::PremultipliedImage cPremultipliedImage(rep.pixelsWide, rep.pixelsHigh);
+ std::copy(rep.bitmapData, rep.bitmapData + cPremultipliedImage.size(), cPremultipliedImage.data.get());
+ return std::make_unique<mbgl::SpriteImage>(std::move(cPremultipliedImage),
+ (float)(rep.pixelsWide / self.size.width));
+}
+
+@end