Skip to content

CompositionLocals

Isometric exposes six public CompositionLocal values that control default rendering behaviour throughout the scene tree. Override any of them with CompositionLocalProvider to change the defaults for an entire subtree without threading parameters through every composable call.

NameTypeDefaultPurpose
LocalDefaultColorIsoColorMaterial Blue IsoColor(33, 150, 243)Color applied to shapes that omit an explicit color parameter
LocalLightDirectionVectorVector(2, -1, 3).normalize()Direction of the scene light source, used for per-face shading
LocalRenderOptionsRenderOptionsRenderOptions.DefaultControls depth sorting, back-face culling, bounds checking
LocalStrokeStyleStrokeStyleStrokeStyle.FillAndStroke()How shape edges are drawn (fill only, stroke only, or both)
LocalColorPaletteColorPaletteColorPalette()Named semantic colors (primary, secondary, accent, etc.)
LocalIsometricEngineIsometricEngineError if not inside a sceneAccess to the projection engine for coordinate conversion

LocalBenchmarkHooks also exists but is internal and defaults to null. It is used exclusively by the benchmark harness and should not be overridden in application code.

Wrap your scene (or any subtree within it) in CompositionLocalProvider to change the ambient values:

@Composable
fun ThemedScene() {
CompositionLocalProvider(
LocalDefaultColor provides IsoColor(76, 175, 80),
LocalLightDirection provides Vector(0.0, -1.0, 2.0).normalize(),
LocalStrokeStyle provides StrokeStyle.FillOnly
) {
IsometricScene {
// All shapes here default to green, top-down lighting, no stroke
Shape(geometry = Prism(Point.ORIGIN, 2.0, 2.0, 2.0))
Shape(geometry = Prism(Point(3.0, 0.0, 0.0), 1.0, 1.0, 3.0))
}
}
}

You can nest providers to apply different themes to different parts of the scene. Each provider overrides only the locals it specifies; the rest inherit from the parent.

@Composable
fun MultiThemeScene() {
IsometricScene {
// Default blue shapes
Shape(geometry = Prism(Point.ORIGIN))
// Red subtree
CompositionLocalProvider(LocalDefaultColor provides IsoColor.RED) {
Shape(geometry = Prism(Point(2.0, 0.0, 0.0)))
Shape(geometry = Prism(Point(2.0, 2.0, 0.0)))
}
// Green subtree with custom palette
CompositionLocalProvider(
LocalDefaultColor provides IsoColor.GREEN,
LocalColorPalette provides ColorPalette(
primary = IsoColor.GREEN,
secondary = IsoColor.CYAN
)
) {
val palette = LocalColorPalette.current
Shape(
geometry = Prism(Point(0.0, 2.0, 0.0)),
color = palette.secondary
)
}
}
}

LocalIsometricEngine provides access to the IsometricEngine instance that drives the current scene. The most common use case is converting between world coordinates and screen coordinates.

@Composable
fun CoordinateDisplay() {
val engine = LocalIsometricEngine.current
val worldPoint = Point(1.0, 1.0, 1.0)
// Convert a world point to screen position
val screenPos = engine.worldToScreen(worldPoint, 800, 600)
Text("Screen position: (${screenPos.x}, ${screenPos.y})")
}

All Isometric locals use staticCompositionLocalOf rather than compositionLocalOf. See Scene Graph — Compose Runtime Integration for why this is the correct trade-off.

ColorPalette groups six named color roles for consistent theming. Read it via LocalColorPalette.current and reference roles by name:

@Composable
fun IsometricScope.PaletteDemo() {
val palette = LocalColorPalette.current
Shape(geometry = Prism(Point.ORIGIN, 2.0, 2.0, 2.0), color = palette.primary)
Shape(geometry = Prism(Point(3.0, 0.0, 0.0)), color = palette.accent)
Shape(geometry = Prism(Point(0.0, 3.0, 0.0)), color = palette.surface)
}

Create a custom palette by constructing a new ColorPalette or calling copy() on an existing one:

val darkPalette = ColorPalette(
primary = IsoColor(30, 30, 30),
secondary = IsoColor(60, 60, 60),
accent = IsoColor(0, 255, 128),
background = IsoColor.BLACK,
surface = IsoColor(50, 50, 50),
error = IsoColor.RED
)
// Or modify an existing palette
val modified = LocalColorPalette.current.copy(accent = IsoColor.YELLOW)