diff options
Diffstat (limited to 'platform/macos/src')
-rw-r--r-- | platform/macos/src/MGLMapView.h | 52 | ||||
-rw-r--r-- | platform/macos/src/MGLMapView.mm | 190 | ||||
-rw-r--r-- | platform/macos/src/Mapbox.h | 5 | ||||
-rw-r--r-- | platform/macos/src/NSColor+MGLAdditions.h | 9 | ||||
-rw-r--r-- | platform/macos/src/NSColor+MGLAdditions.mm | 90 |
5 files changed, 257 insertions, 89 deletions
diff --git a/platform/macos/src/MGLMapView.h b/platform/macos/src/MGLMapView.h index 96b0932c14..e3de1069a9 100644 --- a/platform/macos/src/MGLMapView.h +++ b/platform/macos/src/MGLMapView.h @@ -397,11 +397,10 @@ MGL_EXPORT IB_DESIGNABLE 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, a visible bounds that stretches from Tokyo to San Francisco would have - coordinates of (35.68476, -220.24257) and (37.78428, -122.41310). + 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; @@ -409,11 +408,10 @@ MGL_EXPORT IB_DESIGNABLE Changes the receiver’s viewport to fit the given coordinate bounds, optionally animating the change. - To make the visible bounds go across the antimeridian or international date line, - specify some longitudes less than −180 degrees or greater than 180 degrees. - - For example, a visible bounds that stretches from Tokyo to San Francisco would have - coordinates of (35.68476, -220.24257) and (37.78428, -122.41310). + 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 @@ -723,16 +721,27 @@ MGL_EXPORT IB_DESIGNABLE 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) NS_ARRAY_OF(id <MGLAnnotation>) *selectedAnnotations; /** Selects an annotation and displays a callout popover for it. - If the given annotation is not visible within the current viewport, this method - has no effect. + 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. + + @note In versions prior to `4.0.0` selecting an offscreen annotation did not + change the camera. */ - (void)selectAnnotation:(id <MGLAnnotation>)annotation; @@ -866,9 +875,9 @@ MGL_EXPORT IB_DESIGNABLE 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 `MGLVectorSource` objects but does not include anything - from `MGLRasterSource` objects, or from image, video, or canvas sources, which - are unsupported by this SDK. + `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 @@ -900,7 +909,7 @@ MGL_EXPORT IB_DESIGNABLE Only visible features are returned. To obtain features regardless of visibility, use the - `-[MGLVectorSource featuresInSourceLayersWithIdentifiers:predicate:]` and + `-[MGLVectorTileSource featuresInSourceLayersWithIdentifiers:predicate:]` and `-[MGLShapeSource featuresMatchingPredicate:]` methods on the relevant sources. @note Layer identifiers are not guaranteed to exist across styles or different @@ -963,9 +972,9 @@ MGL_EXPORT IB_DESIGNABLE 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 `MGLVectorSource` objects but does not include anything - from `MGLRasterSource` objects, or from image, video, or canvas sources, which - are unsupported by this SDK. + `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 @@ -998,7 +1007,7 @@ MGL_EXPORT IB_DESIGNABLE Only visible features are returned. To obtain features regardless of visibility, use the - `-[MGLVectorSource featuresInSourceLayersWithIdentifiers:predicate:]` and + `-[MGLVectorTileSource featuresInSourceLayersWithIdentifiers:predicate:]` and `-[MGLShapeSource featuresMatchingPredicate:]` methods on the relevant sources. @note Layer identifiers are not guaranteed to exist across styles or different @@ -1060,6 +1069,9 @@ MGL_EXPORT IB_DESIGNABLE /** 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. diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm index 9ec9012198..9cab9a76da 100644 --- a/platform/macos/src/MGLMapView.mm +++ b/platform/macos/src/MGLMapView.mm @@ -97,6 +97,9 @@ 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 uint32_t MGLAnnotationTag; @@ -2099,44 +2102,41 @@ public: 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()); - + // 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 first selection in the cycle should be the one nearest to the - // click. - 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 distanceA = hypot(coordinateA.latitude - currentCoordinate.latitude, - coordinateA.longitude - currentCoordinate.longitude); - CLLocationDegrees distanceB = hypot(coordinateB.latitude - currentCoordinate.latitude, - 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 - || _lastSelectedAnnotationTag == _annotationsNearbyLastClick.back()) { + || _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 = _annotationsNearbyLastClick.front(); + hitAnnotationTag = nearbyAnnotations.front(); } else { - auto result = std::find(_annotationsNearbyLastClick.begin(), - _annotationsNearbyLastClick.end(), + auto result = std::find(nearbyAnnotations.begin(), + nearbyAnnotations.end(), _lastSelectedAnnotationTag); - if (result == _annotationsNearbyLastClick.end()) { + if (result == nearbyAnnotations.end()) { // An annotation from this set hasn’t been selected before. // Select the first (nearest) one. - hitAnnotationTag = _annotationsNearbyLastClick.front(); + hitAnnotationTag = nearbyAnnotations.front(); } else { // Step to the next annotation in the set. - auto distance = std::distance(_annotationsNearbyLastClick.begin(), result); - hitAnnotationTag = _annotationsNearbyLastClick[distance + 1]; + auto distance = std::distance(nearbyAnnotations.begin(), result); + hitAnnotationTag = nearbyAnnotations[distance + 1]; } } } else { @@ -2208,10 +2208,12 @@ public: return; } - // Select the annotation if it’s visible. - if (MGLCoordinateInCoordinateBounds(firstAnnotation.coordinate, self.visibleCoordinateBounds)) { - [self selectAnnotation:firstAnnotation]; - } + [self selectAnnotation:firstAnnotation]; +} + +- (BOOL)isBringingAnnotationOnscreenSupportedForAnnotation:(id<MGLAnnotation>)annotation animated:(BOOL)animated { + // Consider delegating + return animated && [annotation isKindOfClass:[MGLPointAnnotation class]]; } - (void)selectAnnotation:(id <MGLAnnotation>)annotation @@ -2221,6 +2223,11 @@ public: - (void)selectAnnotation:(id <MGLAnnotation>)annotation atPoint:(NSPoint)gesturePoint { + [self selectAnnotation:annotation atPoint:gesturePoint moveOnscreen:YES animateSelection:YES]; +} + +- (void)selectAnnotation:(id <MGLAnnotation>)annotation atPoint:(NSPoint)gesturePoint moveOnscreen:(BOOL)moveOnscreen animateSelection:(BOOL)animateSelection +{ id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation; if (annotation == selectedAnnotation) { return; @@ -2235,9 +2242,14 @@ public: [self addAnnotation:annotation]; } + if (moveOnscreen) { + moveOnscreen = [self isBringingAnnotationOnscreenSupportedForAnnotation:annotation animated:animateSelection]; + } + // The annotation's anchor will bounce to the current click. NSRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag]; - if (NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) { + + if (!moveOnscreen && NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) { positioningRect = CGRectMake(gesturePoint.x, gesturePoint.y, positioningRect.size.width, positioningRect.size.height); } @@ -2257,11 +2269,65 @@ public: // 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 (moveOnscreen) + { + moveOnscreen = NO; + + NSRect (^edgeInsetsInsetRect)(NSRect, NSEdgeInsets) = ^(NSRect rect, NSEdgeInsets insets) { + return NSMakeRect(rect.origin.x + insets.left, + rect.origin.y + insets.top, + 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 = 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 + if (CGRectGetMinX(positioningRect) < CGRectGetMinX(bounds)) + { + constrainedRect.origin.x = expandedPositioningRect.origin.x; + moveOnscreen = YES; + } + else if (CGRectGetMaxX(positioningRect) > CGRectGetMaxX(bounds)) + { + constrainedRect.origin.x = CGRectGetMaxX(expandedPositioningRect) - constrainedRect.size.width; + moveOnscreen = YES; + } + + if (CGRectGetMinY(positioningRect) < CGRectGetMinY(bounds)) + { + constrainedRect.origin.y = expandedPositioningRect.origin.y; + moveOnscreen = YES; + } + else if (CGRectGetMaxY(positioningRect) > CGRectGetMaxY(bounds)) + { + constrainedRect.origin.y = CGRectGetMaxY(expandedPositioningRect) - constrainedRect.size.height; + moveOnscreen = YES; + } + + if (moveOnscreen) + { + CGPoint center = CGPointMake(CGRectGetMidX(constrainedRect), CGRectGetMidY(constrainedRect)); + CLLocationCoordinate2D centerCoord = [self convertPoint:center toCoordinateFromView:self]; + [self setCenterCoordinate:centerCoord animated:animateSelection]; + } + } } - (void)showAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations animated:(BOOL)animated { @@ -2396,7 +2462,25 @@ public: - (void)updateAnnotationCallouts { NSPopover *callout = self.calloutForSelectedAnnotation; if (callout) { - callout.positioningRect = [self positioningRectForCalloutForAnnotationWithTag:_selectedAnnotationTag]; + NSRect rect = [self positioningRectForCalloutForAnnotationWithTag:_selectedAnnotationTag]; + + 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; + } + } } } @@ -2717,32 +2801,24 @@ public: /// Converts a rectangle in the given view’s coordinate system to a geographic /// bounding box. - (mbgl::LatLngBounds)convertRect:(NSRect)rect toLatLngBoundsFromView:(nullable NSView *)view { - mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - bounds.extend([self convertPoint:rect.origin toLatLngFromView:view]); - 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; - if (bounds.west() > -180) { - outsideLatLng = { - (bounds.south() + bounds.north()) / 2, - bounds.west() - 1, - }; - } else if (bounds.northeast().longitude() < 180) { - outsideLatLng = { - (bounds.south() + bounds.north()) / 2, - 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)); - } + 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; } diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h index 0e4b546cf7..fcf41203cf 100644 --- a/platform/macos/src/Mapbox.h +++ b/platform/macos/src/Mapbox.h @@ -49,11 +49,10 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLOpenGLStyleLayer.h" #import "MGLSource.h" #import "MGLTileSource.h" -#import "MGLVectorSource.h" +#import "MGLVectorTileSource.h" #import "MGLShapeSource.h" -#import "MGLAbstractShapeSource.h" #import "MGLComputedShapeSource.h" -#import "MGLRasterSource.h" +#import "MGLRasterTileSource.h" #import "MGLRasterDEMSource.h" #import "MGLImageSource.h" #import "MGLTilePyramidOfflineRegion.h" diff --git a/platform/macos/src/NSColor+MGLAdditions.h b/platform/macos/src/NSColor+MGLAdditions.h index 8dd8c1c17c..21c939fec6 100644 --- a/platform/macos/src/NSColor+MGLAdditions.h +++ b/platform/macos/src/NSColor+MGLAdditions.h @@ -6,7 +6,7 @@ @interface NSColor (MGLAdditions) /** - Converts the color into an mbgl::Color in calibrated RGB space. + Converts the color into an mbgl::Color in sRGB space. */ - (mbgl::Color)mgl_color; @@ -18,3 +18,10 @@ - (mbgl::style::PropertyValue<mbgl::Color>)mgl_colorPropertyValue; @end + +@interface NSExpression (MGLColorAdditions) + ++ (NSExpression *)mgl_expressionForRGBComponents:(NSArray<NSExpression *> *)components; ++ (NSExpression *)mgl_expressionForRGBAComponents:(NSArray<NSExpression *> *)components; + +@end diff --git a/platform/macos/src/NSColor+MGLAdditions.mm b/platform/macos/src/NSColor+MGLAdditions.mm index 5288f2bc61..8c9086ccf7 100644 --- a/platform/macos/src/NSColor+MGLAdditions.mm +++ b/platform/macos/src/NSColor+MGLAdditions.mm @@ -2,24 +2,98 @@ @implementation NSColor (MGLAdditions) -- (mbgl::Color)mgl_color -{ +- (mbgl::Color)mgl_color { CGFloat r, g, b, a; - [[self colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&r green:&g blue:&b alpha:&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]; return { (float)r, (float)g, (float)b, (float)a }; } -+ (NSColor *)mgl_colorWithColor:(mbgl::Color)color -{ - return [NSColor colorWithCalibratedRed:color.r green:color.g blue:color.b alpha:color.a]; ++ (NSColor *)mgl_colorWithColor:(mbgl::Color)color { + // 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::style::PropertyValue<mbgl::Color>)mgl_colorPropertyValue -{ +- (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_colorWithComponentExpressions: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_colorWithComponentExpressions: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_colorWithComponentExpressions:(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); + } + // 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 |