Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
Runtime styling API for iOS/macOS (#5727)
Browse files Browse the repository at this point in the history
* [ios] wip runtime styling

* [ios, macos] Outlined header templates

* [ios, macos] cleanup and use appropiate data types

* [ios, macos] removed refs to mbgl and added convenient color methods on UIColor and NSColor

* [ios, macos] updated header template, included doc string

* [ios, macos] outlined template for layer implementation files

* [ios, macos] moved script to platform/darwin and updated comments

* [ios, macos] Cleaned up the implementation template

* [ios, macos] removed unused function and added support for more datatypes

* [ios, macos] overhauling the type protocols

* [ios, macos] removed unnecessary style classes

* [ios, macos] added support for more types

* [ios, macos] fixed string and number prop values

* [ios, macos] enum getters

* [ios, macos] added removeLayer() and removed unused layer ref

* [ios, macos] fixed remaining layer types and converted style layer into a protocol

* [ios, macos] fixed addLayer() and added example for line layer

* [ios] GeoJSON source now works

* [ios] fixed raster layer and raster source

* [ios] fixed attr prop number and outlined prop function

* [ios] wip functions

* [ios] bool and float function fix

* [ios] cleanup

* [macos] fixed support for macos

* [ios] fixed string functions

* [ios] extended array functions

* [ios] added tests and fixed a few bugs

* [ios] less verbose functions and improved tests

* [ios, macos] Removed unnecessary use of default arguments

Default arguments aren’t supported in Node v4, and they aren’t needed here anyways, because we’re only testing for truthiness.

* [ios, macos] Enum setters

Rely on a macro instead of category methods to specialize the setter implementation for enum attributes, since generic types are disallowed in Objective-C method signatures and mbgl::style::PropertyType must be type-qualified.

* [ios, macos] Got macOS closer to parity w/ iOS

Added various classes and test classes to the macOS workspace. Also fixed lots of compiler errors and updated macosapp runtime styling example to use the new dictionary syntax for function stops.

Updated all conversions from Objective-C stops to C++ stops to enumerate over the dictionary. Fixed compiler errors in enum setter implementations.

Also corrected path to script in generated file comments.

* [ios, macos] Added EJS templates to project

* [ios, macos] Code formatting

Made code format more consistent.

* [ios, macos] Spelled out ID

Cocoa convention is to always spell out “ID” as “identifier”, in part to avoid confusion with the id keyword. Also, URL is capitalized in most contexts, including property names.

* [ios, macos] Grouped related headers together

* [ios, macos] Cleaned up layer transformation

Also added support for converting background layers into Objective-C.

* [ios, macos] Cleaned up tests

Also made the tests work on macOS by introducing a new window to host the map.

* [ios, macos] Replaced TODOs with #warnings

* [ios] convert array based property values

* [ios, macos] color function/undefined getter

* [ios, macos] fixed function/undefined getters for bool and float properties

* [ios, macos] more function/undefined property getters

* [ios, macos] more type conversion and cleanup

* [ios, macos] disable macos runtime styling tests for now

* [ios, macos] cleaned up style code script

* [ios, macos] more type conversion

* [ios] added a base layer to handle visibility min/max zoom

* [macos] fixed base layer

* [ios, macos] use accessor methods

* [ios, macos] cleanup

* [ios, macos] add geojson to ios and macos

* [macos] rebase fix

* [ios, macos] fixed enum getters and added tests for enums

* [ios, macos] added an update method to base layer

* [ios, macos] added some documentation

* [ios, macos] docs

* [ios, macos] removed refs to filters for now

* [ios, macos] various tail work

* [ios, macos] missing import and incorrect type
  • Loading branch information
frederoni authored Aug 11, 2016
1 parent b0cb871 commit 2354c87
Show file tree
Hide file tree
Showing 96 changed files with 6,841 additions and 53 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ ifabric: $(IOS_PROJ_PATH)
idocument:
OUTPUT=$(OUTPUT) ./platform/ios/scripts/document.sh

style-code-darwin:
node platform/darwin/scripts/generate-style-code.js
endif

#### Linux targets #####################################################
Expand Down
211 changes: 211 additions & 0 deletions platform/darwin/scripts/generate-style-code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
'use strict';

const fs = require('fs');
const ejs = require('ejs');
const spec = require('mapbox-gl-style-spec').latest;

var prefix = 'MGL';
var suffix = 'StyleLayer';

global.camelize = function (str) {
return str.replace(/(?:^|-)(.)/g, function (_, x) {
return x.toUpperCase();
});
};

global.camelizeWithLeadingLowercase = function (str) {
return str.replace(/-(.)/g, function (_, x) {
return x.toUpperCase();
});
};

global.objCName = function (property) { return camelizeWithLeadingLowercase(property.name); }

global.testImplementation = function (property, layerType) {
switch (property.type) {
case 'boolean':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testBool;`;
case 'number':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testNumber;`;
case 'string':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testString;`;
case 'enum':
var objCType = `${prefix}${camelize(layerType)}${suffix}${camelize(property.name)}`;
var objCEnum = `${objCType}${camelize(property.values[property.values.length-1])}`;
return `layer.${objCName(property)} = [MGLRuntimeStylingHelper testEnum:${objCEnum} type:@encode(${objCType})];`;
case 'color':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testColor;`;
case 'array':
return testArrayImplementation(property);
default: throw new Error(`unknown type for ${property.name}`)
}
}

global.testGetterImplementation = function (property, layerType) {
switch (property.type) {
case 'boolean':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testBool);`;
case 'number':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testNumber);`;
case 'string':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testString);`;
case 'enum':
var objCType = `${prefix}${camelize(layerType)}${suffix}${camelize(property.name)}`;
var objCEnum = `${objCType}${camelize(property.values[property.values.length-1])}`;
return `XCTAssert([(NSValue *)gLayer.${objCName(property)} objCType] == [[MGLRuntimeStylingHelper testEnum:${objCEnum} type:@encode(${objCType})] objCType]);`;
case 'color':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testColor);`;
case 'array':
return testGetterArrayImplementation(property);
default: throw new Error(`unknown type for ${property.name}`)
}
}

global.testGetterArrayImplementation = function (property) {
switch (property.name) {
case 'icon-text-fit-padding':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testPadding);`;
case 'line-dasharray':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testDashArray);`;
case 'text-font':
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testFont);`;
default:
return `XCTAssertEqualObjects(gLayer.${objCName(property)}, MGLRuntimeStylingHelper.testOffset);`; // Default offset (dx, dy)
}
};

global.testArrayImplementation = function (property) {
switch (property.name) {
case 'icon-text-fit-padding':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testPadding;`;
case 'line-dasharray':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testDashArray;`;
case 'text-font':
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testFont;`;
default:
return `layer.${objCName(property)} = MGLRuntimeStylingHelper.testOffset;`; // Default offset (dx, dy)
}
};

global.propertyType = function (property, _private) {
return _private ? `id <MGLStyleAttributeValue, MGLStyleAttributeValue_Private>` : `id <MGLStyleAttributeValue>`;
};

global.initLayer = function (layerType) {
if (layerType == "background") {
return `_layer = new mbgl::style::${camelize(layerType)}Layer(layerIdentifier.UTF8String);`
} else {
return `_layer = new mbgl::style::${camelize(layerType)}Layer(layerIdentifier.UTF8String, sourceIdentifier.UTF8String);`
}
}

global.setterImplementation = function(property, layerType) {
switch (property.type) {
case 'boolean':
return `self.layer->set${camelize(property.name)}(${objCName(property)}.mbgl_boolPropertyValue);`;
case 'number':
return `self.layer->set${camelize(property.name)}(${objCName(property)}.mbgl_floatPropertyValue);`;
case 'string':
return `self.layer->set${camelize(property.name)}(${objCName(property)}.mbgl_stringPropertyValue);`;
case 'enum':
var objCType = `${prefix}${camelize(layerType)}${suffix}${camelize(property.name)}`;
return `MGLSetEnumProperty(${objCName(property)}, ${camelize(property.name)}, ${mbglType(property)}, ${objCType});`;
case 'color':
return `self.layer->set${camelize(property.name)}(${objCName(property)}.mbgl_colorPropertyValue);`;
case 'array':
return arraySetterImplementation(property);
default: throw new Error(`unknown type for ${property.name}`)
}
}

global.mbglType = function(property) {
var mbglType = camelize(property.name) + 'Type';
if (/-translate-anchor$/.test(property.name)) {
mbglType = 'TranslateAnchorType';
}
if (/-(rotation|pitch)-alignment$/.test(property.name)) {
mbglType = 'AlignmentType';
}
return mbglType;
}

global.arraySetterImplementation = function(property) {
return `self.layer->set${camelize(property.name)}(${objCName(property)}.mbgl_${convertedType(property)}PropertyValue);`;
}

global.getterImplementation = function(property, layerType) {
switch (property.type) {
case 'boolean':
return `return [MGLStyleAttribute mbgl_boolPropertyValueWith:self.layer->get${camelize(property.name)}()];`
case 'number':
return `return [MGLStyleAttribute mbgl_numberPropertyValueWith:self.layer->get${camelize(property.name)}()];`
case 'string':
return `return [MGLStyleAttribute mbgl_stringPropertyValueWith:self.layer->get${camelize(property.name)}()];`
case 'enum':
var objCType = `${prefix}${camelize(layerType)}${suffix}${camelize(property.name)}`;
return `MGLGetEnumProperty(${camelize(property.name)}, ${mbglType(property)}, ${objCType});`;
case 'color':
return `return [MGLStyleAttribute mbgl_colorPropertyValueWith:self.layer->get${camelize(property.name)}()];`
case 'array':
return arrayGetterImplementation(property);
default:
throw new Error(`unknown type for ${property.name}`)
}
}

global.arrayGetterImplementation = function(property) {
return `return [MGLStyleAttribute mbgl_${convertedType(property)}PropertyValueWith:self.layer->get${camelize(property.name)}()];`
}

global.convertedType = function(property) {
switch (property.name) {
case 'boolean':
return 'bool';
case 'number':
return 'number';
case 'color':
return 'color';
case 'string':
return 'string';
case 'icon-text-fit-padding':
return "padding";
case 'line-dasharray':
return "numberArray";
case 'text-font':
return "stringArray";
default:
return "offset";
}
}

const layerH = ejs.compile(fs.readFileSync('platform/darwin/src/MGLStyleLayer.h.ejs', 'utf8'), { strict: true });
const layerM = ejs.compile(fs.readFileSync('platform/darwin/src/MGLStyleLayer.mm.ejs', 'utf8'), { strict: true});
const testLayers = ejs.compile(fs.readFileSync('platform/darwin/src/MGLRuntimeStylingTests.m.ejs', 'utf8'), { strict: true});

const layers = spec.layer.type.values.map((type) => {
const layoutProperties = Object.keys(spec[`layout_${type}`]).reduce((memo, name) => {
if (name !== 'visibility') {
spec[`layout_${type}`][name].name = name;
memo.push(spec[`layout_${type}`][name]);
}
return memo;
}, []);

const paintProperties = Object.keys(spec[`paint_${type}`]).reduce((memo, name) => {
spec[`paint_${type}`][name].name = name;
memo.push(spec[`paint_${type}`][name]);
return memo;
}, []);

return {
type: type,
layoutProperties: layoutProperties,
paintProperties: paintProperties,
};
});

for (const layer of layers) {
fs.writeFileSync(`platform/darwin/src/${prefix}${camelize(layer.type)}${suffix}.h`, layerH(layer));
fs.writeFileSync(`platform/darwin/src/${prefix}${camelize(layer.type)}${suffix}.mm`, layerM(layer));
fs.writeFileSync(`platform/darwin/test/${prefix}${camelize(layer.type)}${suffix}Tests.m`, testLayers(layer));
}
27 changes: 27 additions & 0 deletions platform/darwin/src/MGLBackgroundStyleLayer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// This file is generated.
// Edit platform/darwin/scripts/generate-style-code.js, then run `make style-code-darwin`.

#import "MGLTypes.h"
#import "MGLStyleAttributeValue.h"
#import "MGLBaseStyleLayer.h"

@interface MGLBackgroundStyleLayer : MGLBaseStyleLayer <MGLStyleLayer>

#pragma mark - Accessing the Paint Attributes

/**
The color with which the background will be drawn.
*/
@property (nonatomic) id <MGLStyleAttributeValue> backgroundColor;

/**
Name of image in sprite to use for drawing an image background. For seamless patterns, image width and height must be a factor of two (2, 4, 8, ..., 512).
*/
@property (nonatomic) id <MGLStyleAttributeValue> backgroundPattern;

/**
The opacity at which the background will be drawn.
*/
@property (nonatomic) id <MGLStyleAttributeValue> backgroundOpacity;

@end
57 changes: 57 additions & 0 deletions platform/darwin/src/MGLBackgroundStyleLayer.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// This file is generated.
// Edit platform/darwin/scripts/generate-style-code.js, then run `make style-code-darwin`.

#import "MGLStyleLayer_Private.hpp"
#import "MGLStyleAttributeValue.h"
#import "MGLBackgroundStyleLayer.h"

#include <mbgl/style/layers/background_layer.hpp>

@interface MGLBackgroundStyleLayer ()

@property (nonatomic) mbgl::style::BackgroundLayer *layer;
@property (nonatomic, readwrite) NSString *layerIdentifier;
@property (nonatomic, readwrite) NSString *sourceIdentifier;

@end

@implementation MGLBackgroundStyleLayer

@synthesize mapView;

- (instancetype)initWithLayerIdentifier:(NSString *)layerIdentifier sourceIdentifier:(NSString *)sourceIdentifier {
if (self = [super init]) {
_layerIdentifier = layerIdentifier;
_sourceIdentifier = sourceIdentifier;
_layer = new mbgl::style::BackgroundLayer(layerIdentifier.UTF8String);
}
return self;
}

#pragma mark - Accessing the Paint Attributes

- (void)setBackgroundColor:(id <MGLStyleAttributeValue, MGLStyleAttributeValue_Private>)backgroundColor {
self.layer->setBackgroundColor(backgroundColor.mbgl_colorPropertyValue);
}

- (id <MGLStyleAttributeValue>)backgroundColor {
return [MGLStyleAttribute mbgl_colorPropertyValueWith:self.layer->getBackgroundColor()];
}

- (void)setBackgroundPattern:(id <MGLStyleAttributeValue, MGLStyleAttributeValue_Private>)backgroundPattern {
self.layer->setBackgroundPattern(backgroundPattern.mbgl_stringPropertyValue);
}

- (id <MGLStyleAttributeValue>)backgroundPattern {
return [MGLStyleAttribute mbgl_stringPropertyValueWith:self.layer->getBackgroundPattern()];
}

- (void)setBackgroundOpacity:(id <MGLStyleAttributeValue, MGLStyleAttributeValue_Private>)backgroundOpacity {
self.layer->setBackgroundOpacity(backgroundOpacity.mbgl_floatPropertyValue);
}

- (id <MGLStyleAttributeValue>)backgroundOpacity {
return [MGLStyleAttribute mbgl_numberPropertyValueWith:self.layer->getBackgroundOpacity()];
}

@end
22 changes: 22 additions & 0 deletions platform/darwin/src/MGLBaseStyleLayer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#import <Foundation/Foundation.h>

@interface MGLBaseStyleLayer : NSObject

@property (nonatomic, assign, getter=isVisible) BOOL visible;

/**
The maximum zoom level on which the layer gets parsed and appears on.
*/
@property (nonatomic, assign) float maximumZoomLevel;

/**
The minimum zoom level on which the layer gets parsed and appears on.
*/
@property (nonatomic, assign) float minimumZoomLevel;

/**
Updates the layer’s layout and paint properties.
*/
- (void)update;

@end
56 changes: 56 additions & 0 deletions platform/darwin/src/MGLBaseStyleLayer.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#import "MGLBaseStyleLayer.h"

#import "MGLStyleLayer_Private.hpp"
#import "MGLMapView_Private.hpp"

#include <mbgl/style/layer.hpp>

@interface MGLBaseStyleLayer() <MGLStyleLayer_Private>
@end

@implementation MGLBaseStyleLayer

@synthesize layerIdentifier;
@synthesize mapView;
@synthesize layer;

- (void)update
{
self.mapView.mbglMap->update(mbgl::Update::RecalculateStyle | mbgl::Update::Classes);
}

- (void)setVisible:(BOOL)visible
{
mbgl::style::VisibilityType v = visible
? mbgl::style::VisibilityType::Visible
: mbgl::style::VisibilityType::None;
self.layer->setVisibility(v);
}

- (BOOL)isVisible
{
mbgl::style::VisibilityType v = self.layer->getVisibility();
return (v == mbgl::style::VisibilityType::Visible);
}

- (void)setMaximumZoomLevel:(float)maximumZoomLevel
{
self.layer->setMaxZoom(maximumZoomLevel);
}

- (float)maximumZoomLevel
{
return self.layer->getMaxZoom();
}

- (void)setMinimumZoomLevel:(float)minimumZoomLevel
{
self.layer->setMinZoom(minimumZoomLevel);
}

- (float)minimumZoomLevel
{
return self.layer->getMinZoom();
}

@end
Loading

0 comments on commit 2354c87

Please sign in to comment.