athevon/genjutsu4 files

Compose Graphics

Advanced Compose visuals - Material 3 Expressive motion physics, AGSL shaders (Android 13+), Canvas/DrawScope generative, graphicsLayer effects.

Specification
Skill ID
athevon/genjutsu/compose-graphics
Publisher
athevon
Repository
genjutsu
Installs
0
Files
4
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

athevon/genjutsu/compose-graphicsInstalls these files
  • SKILL.md
  • references/agsl-recipes.md
  • references/canvas-generative.md
  • references/m3-expressive-deep.md

What this skill tells the agent

Version-sensitive. Every API name, SDK gate and browser-support claim below was verified on 2026-09-08 against primary sources. What against, and when, is in _jutsu/VERSIONS.md. If that date is old, re-verify before acting on a version number.

Compose Graphics

Advanced Compose visuals: M3 Expressive motion physics, AGSL shaders (Android 13+), Canvas / DrawScope, graphicsLayer effects. Loaded for advanced thesis (shader, expressive, M3 Expressive, AGSL, Canvas, holographic). Foundation: ../compose-motion/SKILL.md covers basics. Concise rules here. Deep-dive in references/.

Decision Tree: Which API for Which Need

NeedAPI
Spring physics with bounce / overshootMotionScheme.expressive() (M3 Expressive)
Pixel-level shaderRuntimeShader + Modifier.graphicsLayer { renderEffect = ... } (Android 13+)
Generative drawing (paths, particles, fractals)Canvas { drawScope -> ... }
GPU effects (blur, shadows, color filters)Modifier.graphicsLayer { renderEffect = ... } or Modifier.blur(...)
Adaptive system materials (Material You glassmorphism)Modifier.background(MaterialTheme.colorScheme.surfaceContainerHighest)
Liquid glass on AndroidAGSL shader recipe (no native API like iOS yet)

Domain 1: Material 3 Expressive

What It Is

The 2025 Material 3 evolution introduces spring-based motion physics replacing fixed-duration tweens. New shape morphing API via androidx.graphics.shapes. New MotionScheme selectable on the theme. Aimed at hero moments, key interactions, brand-defining UI.

MotionScheme

SchemePersonalityUse For
MotionScheme.standard()Calmer, less overshootDefault for chrome, lists, navigation
MotionScheme.expressive()More overshoot, longer settleHero reveals, FABs, primary CTAs

Apply on the theme:

MaterialTheme(motionScheme = MotionScheme.expressive()) {
    // children read tokens via MaterialTheme.motionScheme.*
}

Tokens exposed:

TokenDomainSpring (damping / stiffness) - standard... expressive
fastSpatialSpec()Position / size0.9 / 14000.6 / 800
defaultSpatialSpec()Position / size0.9 / 7000.8 / 380
slowSpatialSpec()Position / size0.9 / 3000.8 / 200
fastEffectsSpec()Opacity / color1.0 / 38001.0 / 3800
defaultEffectsSpec()Opacity / color1.0 / 16001.0 / 1600
slowEffectsSpec()Opacity / color1.0 / 8001.0 / 800
Spatial vs Effects: spatial = anything physical (height, offset, scale). Effects = visual properties without inertia (alpha, color, elevation). Both families are springs - the effects ones are just critically damped (dampingRatio = 1.0) and much stiffer, so they never overshoot. Only the spatial springs differ between standard() and expressive().
Availability: MotionScheme, MaterialTheme(motionScheme = ...) and MaterialExpressiveTheme are stable in material3 1.4.0 (no opt-in needed). MaterialShapes and the shape-morphing helpers are not - they need 1.5.0-alpha and @OptIn(ExperimentalMaterial3ExpressiveApi::class).

Hero Card Expand (Expressive Springs)

@Composable
fun ExpressiveHero() {
    var expanded by remember { mutableStateOf(false) }
    MaterialTheme(motionScheme = MotionScheme.expressive()) {
        val transition = updateTransition(targetState = expanded, label = "expand")
        val height by transition.animateDp(
            transitionSpec = { MaterialTheme.motionScheme.slowSpatialSpec() },
            label = "height"
        ) { if (it) 400.dp else 100.dp }
        val alpha by transition.animateFloat(
            transitionSpec = { MaterialTheme.motionScheme.defaultEffectsSpec() },
            label = "alpha"
        ) { if (it) 1f else 0f }

        Card(
            modifier = Modifier
                .fillMaxWidth()
                .height(height)
                .clickable { expanded = !expanded }
        ) {
            Box(modifier = Modifier.alpha(alpha)) {
                Text("Detail content", modifier = Modifier.padding(24.dp))
            }
        }
    }
}

Spring Tuning Recipes

MoodSpec
Hero revealspring(stiffness = Spring.StiffnessLow, dampingRatio = Spring.DampingRatioMediumBouncy)
Snappy expressivespring(stiffness = Spring.StiffnessMediumLow, dampingRatio = 0.7f)
Calm spatialMaterialTheme.motionScheme.defaultSpatialSpec() (use the token)
Critical (no overshoot)spring(stiffness = Spring.StiffnessHigh, dampingRatio = 1f)

Shape Morphing (MaterialShapes + Morph)

Two different libraries, and the split matters:

  • androidx.graphics.shapes:graphics-shapes (stable 1.1.0) ships RoundedPolygon, CornerRounding and Morph(start, end). Its Morph.toPath(progress) returns an `android.graphics.Path`.
  • The predefined Material shapes (MaterialShapes.Circle, Pentagon, Cookie4Sided, Sunny, Heart, ...) live in material3, not in graphics-shapes. They are still @ExperimentalMaterial3ExpressiveApi and ship only in androidx.compose.material3:material3:1.5.0-alpha* - they are not in stable material3 1.4.0. material3 also adds Morph.toPath(progress) returning a Compose `Path`, plus RoundedPolygon.toShape().

Never write .asAndroidPath().asComposePath(): with the graphics-shapes import it does not compile (android.graphics.Path has no asAndroidPath()), and with the material3 import it is a pointless round trip. And because MaterialShapes are normalized to a 0..1 box, the path must be scaled to the draw size - dropping the raw path into GenericShape yields a ~1px shape in the top-left corner.

import androidx.compose.material3.MaterialShapes
import androidx.compose.material3.toPath          // Morph.toPath -> androidx.compose.ui.graphics.Path
import androidx.graphics.shapes.Morph

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MorphingBadge(active: Boolean) {
    val morph = remember { Morph(MaterialShapes.Circle, MaterialShapes.Cookie4Sided) }
    val progress by animateFloatAsState(
        targetValue = if (active) 1f else 0f,
        animationSpec = MaterialTheme.motionScheme.slowSpatialSpec(),
        label = "morph",
    )
    Box(
        modifier = Modifier
            .size(96.dp)
            .clip(MorphShape(morph, progress))
            .background(MaterialTheme.colorScheme.primary)
    )
}

/** MaterialShapes are normalized to 0..1: scale the path to the draw size, then centre it. */
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
private class MorphShape(private val morph: Morph, private val progress: Float) : Shape {
    override fun createOutline(
        size: Size,
        layoutDirection: LayoutDirection,
        density: Density,
    ): Outline {
        val path = morph.toPath(progress = progress)
        path.transform(Matrix().apply { scale(x = size.width, y = size.height) })
        path.translate(size.center - path.getBounds().center)
        return Outline.Generic(path)
    }
}

For a static (non-morphing) shape, skip all of this: MaterialShapes.Cookie4Sided.toShape() returns a ready-to-use Shape that already handles the scaling.

When to Use Expressive vs Standard

  • Expressive: hero moments, key interactions, FABs, primary CTAs. 1-3% of UI.
  • Standard: default for the rest of the app. Mixing too much Expressive feels chaotic - every element fighting for attention.

Domain 2: AGSL Shaders (Android 13+)

What It Is

AGSL is Android's shader language. Similar to GLSL with simplifications (half4 instead of vec4, restricted feature set, sandbox-safe). RuntimeShader compiles your AGSL source. Bind to a Compose modifier via Modifier.graphicsLayer { renderEffect = ... }.

Setup

@Composable

Use this skill in a project

Open any RiverX project, search the Skills panel for athevon/genjutsu/compose-graphics, and install it in one click.

Use this skill