-
Notifications
You must be signed in to change notification settings - Fork 38
Tool for generating iOS/macOS runtime styling code from style diff #642
Conversation
The tool now generates more-or-less an AST of the statements necessary to reproduce the style commands. By default, it outputs the AST in JSON format, but the The Swift is far from idiomatic. It doesn’t take advantage of type inference, which would simplify the code greatly: _ = {
let compositeSource = mapView.style.source(withIdentifier: "composite")!
let placeResidentialLayer = MGLSymbolStyleLayer(identifier: "place-residential", source: compositeSource)
placeResidentialLayer.textLineHeight = MGLStyleValue<NSNumber>(rawValue: 1.2)
placeResidentialLayer.textSize = MGLStyleValue<NSNumber>(stops: [
10: MGLStyleValue<NSNumber>(rawValue: 11),
18: MGLStyleValue<NSNumber>(rawValue: 14),
])
placeResidentialLayer.maximumTextAngle = MGLStyleValue<NSNumber>(rawValue: 38)
placeResidentialLayer.symbolSpacing = MGLStyleValue<NSNumber>(rawValue: 250)
placeResidentialLayer.textFont = MGLStyleValue<NSArray>(rawValue: ["DIN Offc Pro Regular", "Arial Unicode MS Regular"])
placeResidentialLayer.textPadding = MGLStyleValue<NSNumber>(rawValue: 2)
placeResidentialLayer.textOffset = MGLStyleValue<NSValue>(rawValue: NSValue(cgVector: CGVector(dx: 0, dy: 0)))
placeResidentialLayer.textRotationAlignment = MGLStyleValue<NSValue>(rawValue: NSValue(mglTextRotationAlignment: .viewport))
placeResidentialLayer.textField = MGLStyleValue<NSString>(rawValue: "{name_en}")
placeResidentialLayer.maximumTextWidth = MGLStyleValue<NSNumber>(rawValue: 7)
placeResidentialLayer.textColor = MGLStyleValue<UIColor>(rawValue: UIColor(red: 102 / 255.0, green: 79 / 255.0, blue: 61 / 255.0, alpha: 1))
placeResidentialLayer.textHaloColor = MGLStyleValue<UIColor>(rawValue: UIColor(red: 255 / 255.0, green: 255 / 255.0, blue: 255 / 255.0, alpha: 1))
placeResidentialLayer.textHaloWidth = MGLStyleValue<NSNumber>(rawValue: 1)
placeResidentialLayer.textHaloBlur = MGLStyleValue<NSNumber>(rawValue: 0.5)
let placeNeighbourhoodLayer = mapView.style.layer(withIdentifier: "place-neighbourhood")!
mapView.style.insertLayer(placeResidentialLayer, below: placeNeighbourhoodLayer)
let mountainPeakLabelWithElevationLayer = MGLSymbolStyleLayer(identifier: "mountain-peak-label-with-elevation", source: compositeSource)
mountainPeakLabelWithElevationLayer.textLineHeight = MGLStyleValue<NSNumber>(rawValue: 1.1)
mountainPeakLabelWithElevationLayer.textSize = MGLStyleValue<NSNumber>(stops: [
10: MGLStyleValue<NSNumber>(rawValue: 11),
18: MGLStyleValue<NSNumber>(rawValue: 14),
])
mountainPeakLabelWithElevationLayer.iconImageName = MGLStyleValue<NSString>(rawValue: "{maki}-15")
mountainPeakLabelWithElevationLayer.textFont = MGLStyleValue<NSArray>(rawValue: ["DIN Offc Pro Medium", "Arial Unicode MS Regular"])
mountainPeakLabelWithElevationLayer.textOffset = MGLStyleValue<NSValue>(rawValue: NSValue(cgVector: CGVector(dx: 0, dy: 0.65)))
mountainPeakLabelWithElevationLayer.textAnchor = MGLStyleValue<NSValue>(rawValue: NSValue(mglTextAnchor: .top))
mountainPeakLabelWithElevationLayer.textField = MGLStyleValue<NSString>(rawValue: "{name_en}, {elevation_m}m")
mountainPeakLabelWithElevationLayer.textLetterSpacing = MGLStyleValue<NSNumber>(rawValue: 0.01)
mountainPeakLabelWithElevationLayer.maximumTextWidth = MGLStyleValue<NSNumber>(rawValue: 8)
mountainPeakLabelWithElevationLayer.textColor = MGLStyleValue<UIColor>(rawValue: UIColor(red: 34 / 255.0, green: 102 / 255.0, blue: 0 / 255.0, alpha: 1))
mountainPeakLabelWithElevationLayer.textHaloColor = MGLStyleValue<UIColor>(rawValue: UIColor(red: 255 / 255.0, green: 255 / 255.0, blue: 255 / 255.0, alpha: 1))
mountainPeakLabelWithElevationLayer.textHaloWidth = MGLStyleValue<NSNumber>(rawValue: 0.5)
mountainPeakLabelWithElevationLayer.textHaloBlur = MGLStyleValue<NSNumber>(rawValue: 0.5)
let poiScalerank2Layer = mapView.style.layer(withIdentifier: "poi-scalerank2")!
mapView.style.insertLayer(mountainPeakLabelWithElevationLayer, below: poiScalerank2Layer)
}()
…
_ = {
let bridgeOnewayArrowsWhiteLayer = mapView.style.layer(withIdentifier: "bridge-oneway-arrows-white") as! MGLVectorStyleLayer
bridgeOnewayArrowsWhiteLayer.predicate = NSPredicate(format: "%K = 'LineString' AND (class IN {'link', 'trunk'} AND oneway = 'true' AND structure = 'bridge' AND NOT type IN {'primary_link', 'secondary_link', 'tertiary_link'})", "$type")
let aerialwayLayer = mapView.style.layer(withIdentifier: "aerialway")!
mapView.style.removeLayer(aerialwayLayer)
}() And for fun, AppleScript is almost there too: set compositeSource to mapView's style's sourceWithIdentifier:"composite"
set placeResidentialLayer to (the current application's MGLSymbolStyleLayer's alloc)'s initWithIdentifier:"place-residential" source:compositeSource
tell placeResidentialLayer
set its textLineHeight to the current application's MGLStyleValue's valueWithRawValue:1.2
set its textSize to the current application's MGLStyleValue's valueWithStops:{ ¬
|10|: the current application's MGLStyleValue's valueWithRawValue:11, ¬
|18|: the current application's MGLStyleValue's valueWithRawValue:14 ¬
}
set its maximumTextAngle to the current application's MGLStyleValue's valueWithRawValue:38
set its symbolSpacing to the current application's MGLStyleValue's valueWithRawValue:250
set its textFont to the current application's MGLStyleValue's valueWithRawValue:{"DIN Offc Pro Regular", "Arial Unicode MS Regular"}
set its textPadding to the current application's MGLStyleValue's valueWithRawValue:2
set its textOffset to the current application's MGLStyleValue's valueWithRawValue:(the current application's NSValue's valueWithCGVector:{0, 0})
set its textRotationAlignment to the current application's MGLStyleValue's valueWithRawValue:(the current application's NSValue's valueWithMGLTextRotationAlignment:the current application's MGLTextRotationAlignmentViewport)
set its textField to the current application's MGLStyleValue's valueWithRawValue:"{name_en}"
set its maximumTextWidth to the current application's MGLStyleValue's valueWithRawValue:7
end tell
tell placeResidentialLayer
set its textColor to the current application's MGLStyleValue's valueWithRawValue:(the current application's UIColor's colorWithRed:102 / 255.0 green:79 / 255.0 blue:61 / 255.0 alpha:1)
set its textHaloColor to the current application's MGLStyleValue's valueWithRawValue:(the current application's UIColor's colorWithRed:255 / 255.0 green:255 / 255.0 blue:255 / 255.0 alpha:1)
set its textHaloWidth to the current application's MGLStyleValue's valueWithRawValue:1
set its textHaloBlur to the current application's MGLStyleValue's valueWithRawValue:0.5
end tell
set placeNeighbourhoodLayer to mapView's style's layerWithIdentifier:"place-neighbourhood"
tell mapView's style to insertLayer:placeResidentialLayer belowLayer:placeNeighbourhoodLayer
set mountainPeakLabelWithElevationLayer to (the current application's MGLSymbolStyleLayer's alloc)'s initWithIdentifier:"mountain-peak-label-with-elevation" source:compositeSource
tell mountainPeakLabelWithElevationLayer
set its textLineHeight to the current application's MGLStyleValue's valueWithRawValue:1.1
set its textSize to the current application's MGLStyleValue's valueWithStops:{ ¬
|10|: the current application's MGLStyleValue's valueWithRawValue:11, ¬
|18|: the current application's MGLStyleValue's valueWithRawValue:14 ¬
}
set its iconImageName to the current application's MGLStyleValue's valueWithRawValue:"{maki}-15"
set its textFont to the current application's MGLStyleValue's valueWithRawValue:{"DIN Offc Pro Medium", "Arial Unicode MS Regular"}
set its textOffset to the current application's MGLStyleValue's valueWithRawValue:(the current application's NSValue's valueWithCGVector:{0, 0.65})
set its textAnchor to the current application's MGLStyleValue's valueWithRawValue:(the current application's NSValue's valueWithMGLTextAnchor:the current application's MGLTextAnchorTop)
set its textField to the current application's MGLStyleValue's valueWithRawValue:"{name_en}, {elevation_m}m"
set its textLetterSpacing to the current application's MGLStyleValue's valueWithRawValue:0.01
set its maximumTextWidth to the current application's MGLStyleValue's valueWithRawValue:8
end tell
tell mountainPeakLabelWithElevationLayer
set its textColor to the current application's MGLStyleValue's valueWithRawValue:(the current application's UIColor's colorWithRed:34 / 255.0 green:102 / 255.0 blue:0 / 255.0 alpha:1)
set its textHaloColor to the current application's MGLStyleValue's valueWithRawValue:(the current application's UIColor's colorWithRed:255 / 255.0 green:255 / 255.0 blue:255 / 255.0 alpha:1)
set its textHaloWidth to the current application's MGLStyleValue's valueWithRawValue:0.5
set its textHaloBlur to the current application's MGLStyleValue's valueWithRawValue:0.5
end tell
set poiScalerank2Layer to mapView's style's layerWithIdentifier:"poi-scalerank2"
tell mapView's style to insertLayer:mountainPeakLabelWithElevationLayer belowLayer:poiScalerank2Layer
…
set bridgeOnewayArrowsWhiteLayer to mapView's style's layerWithIdentifier:"bridge-oneway-arrows-white"
set bridgeOnewayArrowsWhiteLayer's iconOpacity to the current application's MGLStyleValue's valueWithRawValue:0.5
set bridgeOnewayArrowsWhiteLayer to mapView's style's layerWithIdentifier:"bridge-oneway-arrows-white"
set bridgeOnewayArrowsWhiteLayer's predicate to the current application's NSPredicate's predicateWithFormat_("%K = 'LineString' AND (class IN {'link', 'trunk'} AND oneway = 'true' AND structure = 'bridge' AND NOT type IN {'primary_link', 'secondary_link', 'tertiary_link'})", "$type")
set aerialwayLayer to mapView's style's layerWithIdentifier:"aerialway"
tell mapView's style to removeLayer:aerialwayLayer |
Also treat block statement as a kind of statement.
} | ||
let refLayerId; | ||
let refLayerVar; | ||
if ('ref' in layer) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is incapable of copying over most aspects of a ref
’d layer. A correct implementation would require a second pass over the diff, plus accessing the original style in order to get the required context in all cases. Alternatively, we’d have to enumerate all the layout and paint properties by name, which would bloat the code quite a bit. mapbox/mapbox-gl-native#7600 would make that a lot easier. Fortunately, we’re getting rid of ref
support anyways in mapbox/mapbox-gl-native#6900.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style diffs with refs are as mess. In lib/diff.js
I ignored that refs will ever exist. On the plus side, refs are going away anyway.
What are some use cases you imagine this tool addressing? |
Tighter integration between Mapbox Studio and the iOS and macOS SDKs in the typical runtime styling development workflow. |
Renamed more properties according to mapbox/mapbox-gl-native#7603.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good from an architectural standpoint. Let's merge once CI is 🍏.
MGLMapView.style is becoming optional in mapbox/mapbox-gl-native#7664. Avoid having to support optional chaining in Swift by referring to style directly, since that would be the name of the second parameter to -[MGLMapViewDelegate mapView:didFinishLoadingStyle:], which is the most likely place for the developer to put runtime styling code.
Near as I can tell, the CI errors are caused by this repository’s eslint rules differing from those of GL JS, specifically the omission of |
@1ec5 It's easiest for me if you stick with this repository's eslint rules for the time being. |
Closing. We intend to merge this repository into the mapbox/mapbox-gl-js repository imminently, and it doesn’t make sense to hold up that merge for this proof of concept. My next step is to issue a similar PR against gl-native for this tool. It makes more sense for this tool to live alongside the formal APIs that its generated output makes use of; by contrast, its only relation to GL JS is that it’s written in the same language. Keeping the tool in the gl-native repository will enable it to draw directly from its existing runtime styling codegen files, allowing us to keep this tool in sync with the iOS and macOS SDKs with minimal overhead. We’ll need to find a way for a JavaScript-based tool like Studio to make use of the tool. If it would be problematic for Studio to depend on gl-native, then we can put the tool in a separate repository that depends on gl-native either formally or informally. |
This is a proof of concept of a tool for transforming a style diff into Objective-C code compatible with the runtime styling API in the Mapbox iOS SDK or Mapbox macOS SDK. The code compiles but isn’t particularly idiomatic or efficient. Here’s an example:
In order to support the properties renamed as part of mapbox/mapbox-gl-native#6098, I’ve integrated the overridden properties into the JSON specification as an
sdk-name
member on each property. Extending this tool to support Swift would require for transforming the data into an intermediate, object-oriented representation before doing any string munging.Depends on #641.
/cc @tmcw @scothis