Skip to content

Commit

Permalink
feat: Add bare Kotlin type overloads for expression functions (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
sargunv authored Dec 26, 2024
1 parent 03aa838 commit e1de5e5
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ object AnimatedLayerDemo : Demo {
join = const(LineJoin.Round),
width =
interpolate(
type = exponential(const(1.2f)),
type = exponential(1.2f),
input = zoom(),
7 to const(1.75.dp),
20 to const(22.dp),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ object ClusteredPointsDemo : Demo {
CircleLayer(
id = "clustered-bikes",
source = bikeSource,
filter = feature.has(const("point_count")),
filter = feature.has("point_count"),
color = const(LIME_GREEN),
opacity = const(0.5f),
radius =
step(
input = feature.get(const("point_count")).asNumber(),
input = feature.get("point_count").asNumber(),
fallback = const(15.dp),
25 to const(20.dp),
100 to const(30.dp),
Expand All @@ -117,16 +117,16 @@ object ClusteredPointsDemo : Demo {
SymbolLayer(
id = "clustered-bikes-count",
source = bikeSource,
filter = feature.has(const("point_count")),
textField = feature.get(const("point_count_abbreviated")).asString(),
filter = feature.has("point_count"),
textField = feature.get("point_count_abbreviated").asString(),
textFont = const(listOf("Noto Sans Regular")),
textColor = const(MaterialTheme.colorScheme.onBackground),
)

CircleLayer(
id = "unclustered-bikes-shadow",
source = bikeSource,
filter = !feature.has(const("point_count")),
filter = !feature.has("point_count"),
radius = const(13.dp),
color = const(Color.Black),
blur = const(1f),
Expand All @@ -136,7 +136,7 @@ object ClusteredPointsDemo : Demo {
CircleLayer(
id = "unclustered-bikes",
source = bikeSource,
filter = !feature.has(const("point_count")),
filter = !feature.has("point_count"),
color = const(LIME_GREEN),
radius = const(7.dp),
strokeWidth = const(3.dp),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ object MarkersDemo : Demo {
iconImage = image(marker),
textField =
format(
span(image(const("railway"))),
span(const(" ")),
span(get(const("STNCODE")).asString(), textSize = const(1.2f.em)),
span(image("railway")),
span(" "),
span(get("STNCODE").asString(), textSize = const(1.2f.em)),
),
textFont = const(listOf("Noto Sans Regular")),
textColor = const(MaterialTheme.colorScheme.onBackground),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ fun Layers() {
color = const(Color.Blue),
width =
interpolate(
type = exponential(const(1.2f)),
type = exponential(1.2f),
input = zoom(),
5 to const(0.4.dp),
6 to const(0.7.dp),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public operator fun <T : ExpressionValue> Expression<ListValue<T>>.get(
index: Expression<IntValue>
): Expression<T> = FunctionCall.of("at", index, this).cast()

/** Returns the item at [index]. */
@JvmName("getAt")
public operator fun <T : ExpressionValue> Expression<ListValue<T>>.get(index: Int): Expression<T> =
get(const(index))

/** Returns whether this list contains the [item]. */
@JvmName("containsList")
public fun <T : ExpressionValue> Expression<ListValue<T>>.contains(
Expand All @@ -39,6 +44,16 @@ public fun <T : ExpressionValue> Expression<ListValue<T>>.indexOf(
return FunctionCall.of("index-of", *args.toTypedArray()).cast()
}

/**
* Returns the first index at which the [item] is located in this list, or `-1` if it cannot be
* found. Accepts an optional [startIndex] from where to begin the search.
*/
@JvmName("indexOfList")
public fun <T : ExpressionValue> Expression<ListValue<T>>.indexOf(
item: Expression<T>,
startIndex: Int? = null,
): Expression<IntValue> = indexOf(item, startIndex?.let { const(it) })

/**
* Returns the items in this list from the [startIndex] (inclusive) to the end of this list if
* [endIndex] is not specified or `null`, otherwise to [endIndex] (exclusive).
Expand All @@ -55,6 +70,15 @@ public fun <T : ExpressionValue> Expression<ListValue<T>>.slice(
return FunctionCall.of("slice", *args.toTypedArray()).cast()
}

/**
* Returns the items in this list from the [startIndex] (inclusive) to the end of this list if
* [endIndex] is not specified or `null`, otherwise to [endIndex] (exclusive).
*/
public fun <T : ExpressionValue> Expression<ListValue<T>>.slice(
startIndex: Int,
endIndex: Int? = null,
): Expression<ListValue<T>> = slice(const(startIndex), endIndex?.let { const(it) })

/** Gets the length of a this list. */
@JvmName("lengthOfList")
public fun Expression<ListValue<*>>.length(): Expression<IntValue> =
Expand All @@ -65,6 +89,13 @@ public operator fun <T : ExpressionValue> Expression<MapValue<T>>.get(
key: Expression<StringValue>
): Expression<T> = FunctionCall.of("get", key, this).cast()

/** Returns the value corresponding the given [key] or `null` if it is not present in this map. */
public operator fun <T : ExpressionValue> Expression<MapValue<T>>.get(key: String): Expression<T> =
get(const(key))

/** Returns whether the given [key] is in this map. */
public fun Expression<MapValue<*>>.has(key: Expression<StringValue>): Expression<BooleanValue> =
FunctionCall.of("has", key, this).cast()

/** Returns whether the given [key] is in this map. */
public fun Expression<MapValue<*>>.has(key: String): Expression<BooleanValue> = has(const(key))
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@ import kotlin.jvm.JvmName
* to `true`, or the [fallback] value otherwise.
*
* Example:
* ```
* ```kt
* switch(
* condition(
* test = feature.has(const("color1")) and feature.has(const("color2")),
* test = feature.has("color1") and feature.has("color2"),
* output = interpolate(
* linear(),
* zoom(),
* 1 to feature.get(const("color1")).convertToColor(),
* 20 to feature.get(const("color2")).convertToColor()
* 1 to feature.get("color1").convertToColor(),
* 20 to feature.get("color2").convertToColor()
* ),
* ),
* condition(
* test = feature.has(const("color")),
* output = feature.get(const("color")).convertToColor(),
* test = feature.has("color"),
* output = feature.get("color").convertToColor(),
* ),
* fallback = const(Color.Red),
* )
Expand Down Expand Up @@ -75,9 +75,9 @@ public fun <T : ExpressionValue> condition(
* will be the [fallback] value.
*
* Example:
* ```
* ```kt
* switch(
* input = feature.get(const("building_type")).asString(),
* input = feature.get("building_type").asString(),
* case(
* label = "residential",
* output = const(Color.Cyan),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@ public object Feature {
*/
public fun get(key: Expression<StringValue>): Expression<*> = FunctionCall.of("get", key)

/**
* Returns the value corresponding to the given [key] in the current feature's properties or
* `null` if it is not present.
*/
public fun get(key: String): Expression<*> = get(const(key))

/** Tests for the presence of a property value [key] in the current feature's properties. */
public fun has(key: Expression<StringValue>): Expression<BooleanValue> =
FunctionCall.of("has", key).cast()

/** Tests for the presence of a property value [key] in the current feature's properties. */
public fun has(key: String): Expression<BooleanValue> = has(const(key))

/**
* Gets the feature properties object. Note that in some cases, it may be more efficient to use
* [get]`("property_name")` directly.
Expand All @@ -43,10 +52,27 @@ public object Feature {
* or any primitive data type. Note that [state] can only be used with layer properties that
* support data-driven styling.
*/
// TODO: document which layer properties support feature state expressions on which platforms
public fun <T : ExpressionValue> state(key: Expression<StringValue>): Expression<T> =
FunctionCall.of("feature-state", key).cast()

/**
* **Note: Not supported on native platforms. See
* [maplibre-native#1698](https://github.com/maplibre/maplibre-native/issues/1698)**
*
* Retrieves a property value from the current feature's state. Returns `null` if the requested
* property is not present on the feature's state.
*
* A feature's state is not part of the GeoJSON or vector tile data, and must be set
* programmatically on each feature.
*
* When `source.promoteId` is not provided, features are identified by their `id` attribute, which
* must be an integer or a string that can be cast to an integer. When `source.promoteId` is
* provided, features are identified by their `promoteId` property, which may be a number, string,
* or any primitive data type. Note that [state] can only be used with layer properties that
* support data-driven styling.
*/
public fun <T : ExpressionValue> state(key: String): Expression<T> = state(const(key))

/** Gets the feature's geometry type. */
public fun type(): Expression<GeometryType> = FunctionCall.of("geometry-type").cast()

Expand All @@ -60,13 +86,26 @@ public object Feature {
public fun lineProgress(value: Expression<FloatValue>): Expression<FloatValue> =
FunctionCall.of("line-progress", value).cast()

/**
* Gets the progress along a gradient line. Can only be used in the `gradient` property of a line
* layer, see [LineLayer][dev.sargunv.maplibrecompose.compose.layer.LineLayer].
*/
public fun lineProgress(value: Float): Expression<FloatValue> = lineProgress(const(value))

/**
* Gets the value of a cluster property accumulated so far. Can only be used in the
* `clusterProperties` option of a clustered GeoJSON source, see
* [GeoJsonOptions][dev.sargunv.maplibrecompose.core.source.GeoJsonOptions].
*/
public fun accumulated(key: Expression<StringValue>): Expression<*> =
FunctionCall.of("accumulated", key)

/**
* Gets the value of a cluster property accumulated so far. Can only be used in the
* `clusterProperties` option of a clustered GeoJSON source, see
* [GeoJsonOptions][dev.sargunv.maplibrecompose.core.source.GeoJsonOptions].
*/
public fun <T : ExpressionValue> accumulated(key: Expression<StringValue>): Expression<T> =
FunctionCall.of("accumulated", key).cast()
public fun accumulated(key: String): Expression<*> = accumulated(const(key))
}

/** Accesses to feature-related data */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package dev.sargunv.maplibrecompose.expressions.dsl

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.TextUnit
import dev.sargunv.maplibrecompose.expressions.ast.Expression
import dev.sargunv.maplibrecompose.expressions.ast.FunctionCall
import dev.sargunv.maplibrecompose.expressions.ast.Options
import dev.sargunv.maplibrecompose.expressions.value.ColorValue
import dev.sargunv.maplibrecompose.expressions.value.FormattableValue
import dev.sargunv.maplibrecompose.expressions.value.FormattedValue
import dev.sargunv.maplibrecompose.expressions.value.StringValue
Expand All @@ -14,13 +17,13 @@ import dev.sargunv.maplibrecompose.expressions.value.TextUnitValue
* string literal or expression, including an [image] expression.
*
* Example:
* ```
* ```kt
* format(
* span(
* feature.get("name").asString().substring(const(0), const(1)).uppercase(),
* feature.get("name").asString().substring(0, 1).uppercase(),
* textScale = const(1.5f),
* ),
* span(feature.get("name").asString().substring(const(1)))
* span(feature.get("name").asString().substring(1))
* )
* ```
*
Expand All @@ -41,11 +44,25 @@ public fun format(vararg spans: FormatSpan): Expression<FormattedValue> =
public fun span(
value: Expression<StringValue>,
textFont: Expression<StringValue>? = null,
textColor: Expression<StringValue>? = null,
textColor: Expression<ColorValue>? = null,
textSize: Expression<TextUnitValue>? = null,
): FormatSpan =
FormatSpan(value = value, textFont = textFont, textColor = textColor, textSize = textSize)

/** Configures a span of text in a [format] expression. */
public fun span(
value: String,
textFont: String? = null,
textColor: Color? = null,
textSize: TextUnit? = null,
): FormatSpan =
span(
value = const(value),
textFont = textFont?.let { const(it) },
textColor = textColor?.let { const(it) },
textSize = textSize?.let { const(it) },
)

/** Configures an image in a [format] expression. */
public fun span(value: Expression<FormattableValue>): FormatSpan = FormatSpan(value = value)

Expand All @@ -54,7 +71,7 @@ public data class FormatSpan
internal constructor(
val value: Expression<FormattableValue>,
val textFont: Expression<StringValue>? = null,
val textColor: Expression<StringValue>? = null,
val textColor: Expression<ColorValue>? = null,
val textSize: Expression<TextUnitValue>? = null,
) {
internal val options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ import dev.sargunv.maplibrecompose.expressions.value.StringValue
public fun image(value: Expression<StringValue>): Expression<ImageValue> =
FunctionCall.of("image", value).cast()

/**
* Returns an image type for use in `iconImage` (see
* [SymbolLayer][dev.sargunv.maplibrecompose.compose.layer.SymbolLayer]), `pattern` entries (see
* [BackgroundLayer][dev.sargunv.maplibrecompose.compose.layer.BackgroundLayer],
* [FillLayer][dev.sargunv.maplibrecompose.compose.layer.FillLayer],
* [FillExtrusionLayer][dev.sargunv.maplibrecompose.compose.layer.FillExtrusionLayer],
* [LineLayer][dev.sargunv.maplibrecompose.compose.layer.LineLayer]) and as a section in the
* [format] expression.
*
* The image argument will check that the requested image exists in the style and will return either
* the resolved image name or `null`, depending on whether or not the image is currently in the
* style. This validation process is synchronous and requires the image to have been added to the
* style before requesting it in the image argument.
*/
public fun image(value: String): Expression<ImageValue> = image(const(value))

/**
* Returns an image type for use in `iconImage` (see
* [SymbolLayer][dev.sargunv.maplibrecompose.compose.layer.SymbolLayer]), `pattern` entries (see
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,4 @@ public fun offset(x: TextUnit, y: TextUnit): Expression<TextUnitOffsetValue> =
* For simplicity, the expression type system does not encode nullability, so the return value of
* this function is assignable to any kind of expression.
*/
public fun <T : ExpressionValue> nil(): Expression<T> = NullLiteral.cast()
public fun nil(): Expression<Nothing> = NullLiteral.cast()
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import dev.sargunv.maplibrecompose.expressions.value.NumberValue
import kotlin.jvm.JvmName

/** Returns mathematical constant ln(2) = natural logarithm of 2. */
public fun ln2(): Expression<FloatValue> = FunctionCall.of("ln2").cast()
public val LN_2: Expression<FloatValue> = FunctionCall.of("ln2").cast()

/** Returns the mathematical constant π */
public fun pi(): Expression<FloatValue> = FunctionCall.of("pi").cast()
public val PI: Expression<FloatValue> = FunctionCall.of("pi").cast()

/** Returns the mathematical constant e */
public fun e(): Expression<FloatValue> = FunctionCall.of("e").cast()
public val E: Expression<FloatValue> = FunctionCall.of("e").cast()

/** Returns the sum of this number expression with [other]. */
public operator fun <U, V : NumberValue<U>> Expression<V>.plus(
Expand Down
Loading

0 comments on commit e1de5e5

Please sign in to comment.