feat(theme): tabular figures on the roles that carry a running number
M0 said the big-readout typography would be settled once there was a stopwatch to settle it against. `ClockulaTypography` now puts `tnum` on the display, headline and title roles, which is every role a counting number uses, and `ClockulaReadoutDefaults` names the three sizes the readouts share. Digits stop changing width as they change, so the timer row, the setup panel and the live pill stop twitching too — none of them needed a call-site change to get it.
This commit is contained in:
@@ -1,10 +1,71 @@
|
|||||||
package de.jeanlucmakiola.clockula.ui.theme
|
package de.jeanlucmakiola.clockula.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
|
||||||
|
/** Fixed-width digits. One string, one place, and a build rule that keeps it there (M7 D12). */
|
||||||
|
private const val TABULAR_FIGURES = "tnum"
|
||||||
|
|
||||||
|
private fun TextStyle.tabular(): TextStyle = copy(fontFeatureSettings = TABULAR_FIGURES)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Material 3 Expressive typography. A clock app is mostly one enormous
|
* The Material 3 Expressive default with **tabular figures** on the display,
|
||||||
* number, so the readout scale is the real typographic decision here — it gets
|
* headline and title roles. A clock app is mostly one enormous number, and a
|
||||||
* settled against a working stopwatch in M7, not guessed at in the scaffolding.
|
* readout whose glyph columns change width as its digits change jitters on
|
||||||
|
* every tick — so every existing readout (the timer row's `headlineLarge`, the
|
||||||
|
* setup panel's `displayMedium`, the live pill's `titleMedium`) is fixed here,
|
||||||
|
* with no call-site change anywhere.
|
||||||
|
*
|
||||||
|
* Body and label roles keep proportional figures: prose is not a column of
|
||||||
|
* numbers, and proportional digits read better inside a sentence.
|
||||||
*/
|
*/
|
||||||
val ClockulaTypography = Typography()
|
val ClockulaTypography: Typography = Typography().run {
|
||||||
|
copy(
|
||||||
|
displayLarge = displayLarge.tabular(),
|
||||||
|
displayMedium = displayMedium.tabular(),
|
||||||
|
displaySmall = displaySmall.tabular(),
|
||||||
|
headlineLarge = headlineLarge.tabular(),
|
||||||
|
headlineMedium = headlineMedium.tabular(),
|
||||||
|
headlineSmall = headlineSmall.tabular(),
|
||||||
|
titleLarge = titleLarge.tabular(),
|
||||||
|
titleMedium = titleMedium.tabular(),
|
||||||
|
titleSmall = titleSmall.tabular(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The three roles a readout uses, named once for the whole app — M0's written
|
||||||
|
* promise that the big-readout scale would be settled against a working
|
||||||
|
* stopwatch rather than guessed at in the scaffolding (M7 D12).
|
||||||
|
*
|
||||||
|
* The M3 `*Defaults` shape — composable getters over `MaterialTheme.typography`
|
||||||
|
* rather than constants — so a theme change still flows through them. No new
|
||||||
|
* font, no size outside the M3 scale, and no weight above Medium: `PLAN.md` §8's
|
||||||
|
* "Expressive ≠ big or bold" applies hardest to the one screen most tempted to
|
||||||
|
* break it.
|
||||||
|
*/
|
||||||
|
object ClockulaReadoutDefaults {
|
||||||
|
|
||||||
|
/** The stopwatch's big number. */
|
||||||
|
val Hero: TextStyle
|
||||||
|
@Composable @ReadOnlyComposable
|
||||||
|
get() = MaterialTheme.typography.displayLarge.copy(fontWeight = FontWeight.Medium)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hundredths beside [Hero], about half its size. A readout whose
|
||||||
|
* hundredths are as large as its seconds makes the fastest-changing glyphs
|
||||||
|
* the loudest ones and drags the eye off the number that matters (D5).
|
||||||
|
*/
|
||||||
|
val HeroFraction: TextStyle
|
||||||
|
@Composable @ReadOnlyComposable
|
||||||
|
get() = MaterialTheme.typography.headlineMedium
|
||||||
|
|
||||||
|
/** A figure inside a table row — the one place body-scale text *is* a column of numbers. */
|
||||||
|
val Figure: TextStyle
|
||||||
|
@Composable @ReadOnlyComposable
|
||||||
|
get() = MaterialTheme.typography.bodyLarge.tabular()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user