Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement Scalebar #232

Merged
merged 24 commits into from
Jan 8, 2025
Merged

Reimplement Scalebar #232

merged 24 commits into from
Jan 8, 2025

Conversation

westnordost
Copy link
Collaborator

@westnordost westnordost commented Jan 6, 2025

Screen_recording_20250106_222520.mp4

Reimplementation of scalebar. Somewhat similar to the Google Maps one, only better :-)

  • can be aligned horizontally to start, end, center or anything in-between (i.e. also layout direction aware)
  • text size and style can be specified, defaults to labelSmall
  • both scale bar and text have a consistent halo and color (defaults to material3 surface and onSurface)
  • can show metric, imperial or both. Will select a reasonable default based on the user's locale or system setting (if applicable)
  • automatically a (minimum) size is selected so that the text does not overlap with the scalebar. Setting a bigger size is fine, too

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

spotless

[spotless] reported by reviewdog 🐶

private val METRIC_STOPS: List<Float> = listOf(
0.1f,
0.2f,
0.5f,
1f,
2f,
5f,
10f,
20f,
50f,
100f,
200f,
500f,
1000f,
2000f,
5000f,
10000f,
20000f,
50000f,
100000f,
200000f,
500000f,
1000000f,
2000000f,
5000000f,
10000000f,
20000000f,
40000000f,
)


[spotless] reported by reviewdog 🐶

private val IMPERIAL_STOPS: List<Float> = listOf(
0.1f,
0.2f,
0.5f,
1f,
2f,
5f,
10f,
20f,
50f,
100f,
200f,
500f,
1000f,
2000f,
1f * FEET_IN_MILE,
2f * FEET_IN_MILE,
5f * FEET_IN_MILE,
10f * FEET_IN_MILE,
20f * FEET_IN_MILE,
50f * FEET_IN_MILE,
100f * FEET_IN_MILE,
200f * FEET_IN_MILE,
500f * FEET_IN_MILE,
1000f * FEET_IN_MILE,
2000f * FEET_IN_MILE,
5000f * FEET_IN_MILE,
10000f * FEET_IN_MILE,
20000f * FEET_IN_MILE,
)


[spotless] reported by reviewdog 🐶


[spotless] reported by reviewdog 🐶


[spotless] reported by reviewdog 🐶

"AG", "AI", "BM", "BS", "FK", "GB", "GD", "GG", "GI", "GS", "IM", "JE", "KN", "KY", "LC",
"MS", "TC", "VG", "VC" -> {


[spotless] reported by reviewdog 🐶


[spotless] reported by reviewdog 🐶

internal actual fun scaleBareMeasurePreference(): ScaleBarMeasure? =
null


[spotless] reported by reviewdog 🐶

internal actual fun scaleBareMeasurePreference(): ScaleBarMeasure? =
null


[spotless] reported by reviewdog 🐶

internal actual fun scaleBareMeasurePreference(): ScaleBarMeasure? =
null

@sargunv
Copy link
Owner

sargunv commented Jan 7, 2025

(I can also pick this up from here and wrap up Apple implementation and the primary/secondary thing if you'd like)

@westnordost
Copy link
Collaborator Author

I am currently looking into the yard+mile stuff

westnordost and others added 2 commits January 7, 2025 22:51
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Comment on lines 296 to 322
0.1f,
0.2f,
0.5f,
1f,
2f,
5f,
10f,
20f,
50f,
100f,
200f,
500f,
1000f,
2000f,
5000f,
10000f,
20000f,
50000f,
100000f,
200000f,
500000f,
1000000f,
2000000f,
5000000f,
10000000f,
20000000f,
40000000f,
Copy link
Owner

@sargunv sargunv Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these can be generated, something like:

private fun buildStops(mantissas: Set<Int>, exponents: IntRange) = buildList {
    for (e in exponents)
        for (m in mantissas)
            add(m.toDouble() * Math.pow(10.0, e.toDouble()))
}

private val METRIC_STOPS = buildStops(mantissas = setOf(1, 2, 5), exponents = -1..7)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks quite ingenious! For me at least, this would introduce a readability problem however, as I find it quite hard to understand what would be the resulting list.

This is the sort of complexity where I'd say that this needs a test case. But then, at the end, it will probably not really be less lines of code.

But anyway, if you want to go ahead with this anyway, I don't mind much.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me at least, this would introduce a readability problem however, as I find it quite hard to understand what would be the resulting list.

I considered that, but I think it's mitigated with just a comment explaining it's based on scientific notation: m*10^e.

if you want to go ahead with this anyway, I don't mind much.

sure, I'll take care of it; just need to tweak the above pattern for the transition between feet/yards and miles.

@westnordost
Copy link
Collaborator Author

hmm, maybe the Measure should be an interface... experimenting with an implementation

# Conflicts:
#	lib/maplibre-compose-material3/src/commonMain/kotlin/dev/sargunv/maplibrecompose/material3/controls/ScaleBar.kt
@westnordost
Copy link
Collaborator Author

Alright, done! One thing I am a bit worried about is whether the ScaleBarMeasure .Metric etc. are still considered as @Stable by Compose. If they are not, then this would lead to the widget being recomposed every frame, even if nothing changed. (Because the List of stops might have changed)

See also https://developer.android.com/develop/ui/compose/performance/stability/fix#immutable-collections

Should this library made to depend on kotlinx.collections.immutable to fix this?

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@sargunv
Copy link
Owner

sargunv commented Jan 8, 2025

One thing I am a bit worried about is whether the ScaleBarMeasure .Metric etc. are still considered as @stable by Compose.

Strong skipping should prevent that, right? We can also tag them with @Immutable

@westnordost
Copy link
Collaborator Author

westnordost commented Jan 8, 2025

I thought strong skipping is only for automatic lambda memoization?

The docs make it sound like I have to annotate the thing as @Stable to enable the .equals(...) check

If we just annotate it as @Immutable, we are handing out guarantees that are out of our control. (ScaleBarMeasure is not sealed, because it doesn't have to be, and to enable funny 1st of April measures 🤩)

https://knowyourmeme.com/memes/banana-for-scale

@sargunv
Copy link
Owner

sargunv commented Jan 8, 2025

separately, I'm tempted to experiment with https://github.com/kevincianfarini/alchemist to represent the length units and their conversion using their Length value class (like Duration); unsure if it'll be worth the extra dependency but I'll try it and see how it looks.

@sargunv
Copy link
Owner

sargunv commented Jan 8, 2025

If we just annotate it as @immutable, we are handing out guarantees that are out of our control.

ah yeah, I was thinking just our implementations should be @Immutable. But I don't know, with polymorphic parameters, does the compose runtime consider immutability of the actual value at runtime, or just the declared type?

The docs make it sound like I have to annotate the thing as @stable to enable the .equals(...) check

I think @Immutable is also considered a "stable" value by the compose compiler, not just @Stable.

I thought strong skipping is only for automatic lambda memoization?

https://developer.android.com/develop/ui/compose/performance/stability/strongskipping#when-skip

Strong Skipping is a mode available in the Compose compiler. When enabled, it changes the compiler's behavior in two ways:

  • Composables with unstable parameters become skippable
  • Lambdas with unstable captures are remembered

@westnordost
Copy link
Collaborator Author

Skippable: If the compiler marks a composable as skippable, Compose can skip it during recomposition if all its arguments are equal with their previous values.

Hmm okay, sounds like we are good(?)

@sargunv
Copy link
Owner

sargunv commented Jan 8, 2025

Hmm okay, sounds like we are good(?)

yup, object combined with referential equality means we should be fine even without @Immutable

@westnordost
Copy link
Collaborator Author

westnordost commented Jan 8, 2025

Screen_recording_20250108_014006.mp4

20 Megabananas!

@sargunv sargunv enabled auto-merge (squash) January 8, 2025 04:14
@sargunv sargunv merged commit ef3ff80 into main Jan 8, 2025
9 checks passed
@sargunv sargunv deleted the scalebar2 branch January 8, 2025 04:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants