Skip to content

Architecture

Haze v2 is built around a flexible architecture that supports multiple visual effects beyond just blur. This document explains the core concepts and how to extend Haze with custom effects.

Visual Effects Framework

At the heart of Haze is the VisualEffect interface, which defines a common contract for all visual effects. This allows Haze to be extensible and support different types of effects on different platforms.

VisualEffect Interface

The VisualEffect interface is the base contract for all visual effects. Implementations provide platform-specific rendering logic for creating the desired visual effect.

interface VisualEffect {
    fun attach(context: VisualEffectContext)
    fun update(context: VisualEffectContext)
    fun detach()
    fun DrawScope.draw(context: VisualEffectContext)
}
  • attach: Called when the effect is first attached to a composable. Implementations can initialize platform-specific resources.
  • update: Called whenever the effect should update its state from composition locals or other sources.
  • detach: Called when the effect is removed. Implementations should clean up resources.
  • draw: Renders the effect using the DrawScope receiver.

HazeEffectScope

The HazeEffectScope is a receiver scope passed to the lambda block of Modifier.hazeEffect. It provides common properties that apply across all effects:

modifier = Modifier.hazeEffect(state) {
    inputScale = HazeInputScale.Auto
    drawContentBehind = true
    // Effect-specific configuration
}

Common properties include: - inputScale: Controls the resolution at which the effect is rendered (performance optimization) - drawContentBehind: Whether to draw the source content before applying the effect - canDrawArea: Optional filter to control which layers are included in the effect

Modular Effect Architecture

Each effect is provided in a separate module, allowing you to include only the effects you need:

  • haze - Core infrastructure (VisualEffect, HazeState, modifiers)
  • haze-blur - Blur effect implementation
  • haze-materials - Pre-built blur styles (Material, Cupertino, Fluent)
  • haze-utils - Shared utilities for platform-specific rendering

Module Dependencies

haze (core)
├── haze-blur (blur effect)
│   └── haze-materials (blur styles)
└── haze-utils (platform utilities)

Future effects will follow the same pattern: - haze-liquidglass - Refraction-based liquid glass effect - Custom third-party effect modules

Effect Registration Pattern

Effect implementations provide builder extension functions on HazeEffectScope for convenient configuration:

// Blur effect example
modifier = Modifier.hazeEffect(state) {
    blurEffect {
        style = HazeMaterials.thin()
        progressive = HazeProgressive.verticalGradient(...)
    }
}

Future effects will follow the same pattern:

modifier = Modifier.hazeEffect(state) {
    liquidglassEffect {
        // liquidglass-specific properties
    }
}

Platform Support

Haze provides a VisualEffectContext that gives effects access to geometry, configuration, and platform capabilities:

interface VisualEffectContext {
    val position: Offset               // Position of the effect node
    val size: Size                     // Size of the effect area
    val layerSize: Size                // Size of the graphics layer (may differ from size)
    val layerOffset: Offset            // Graphics layer offset relative to node position
    val rootBounds: Rect               // Bounds of the root layout coordinates on screen
    val inputScale: HazeInputScale     // Input scale factor configuration
    val windowId: Any?                 // Identifier for the containing window
    val areas: List<HazeArea>          // Source areas this effect should process
    val state: HazeState?              // Associated HazeState (null for foreground blur)
    val coroutineScope: CoroutineScope // CoroutineScope tied to the node lifecycle

    fun requireDensity(): Density
    fun <T> currentValueOf(local: CompositionLocal<T>): T
    fun requireGraphicsContext(): GraphicsContext
    fun invalidateDraw()
}

Effects can detect platform capabilities and provide the best implementation available. For example:

  • Android 13+: Uses RenderEffect.createBlurEffect()
  • Android 12: Uses RenderNode with shader-based blur
  • Android 11 and below: Uses Renderscript blur
  • Desktop/iOS: Uses Skia shaders
  • Web: Uses canvas filters
  • WASM: Uses custom shader implementations

Implementing Custom Effects

To create a custom effect, implement the VisualEffect interface and provide a builder extension:

class CustomVisualEffect : VisualEffect {
    override fun attach(context: VisualEffectContext) {
        // Initialize resources
    }

    override fun update(context: VisualEffectContext) {
        // Update state from composition locals or snapshot state
    }

    override fun detach() {
        // Clean up resources
    }

    override fun DrawScope.draw(context: VisualEffectContext) {
        // Render the effect using the DrawScope receiver
    }
}

// Provide a builder extension
fun HazeEffectScope.customEffect(block: CustomVisualEffect.() -> Unit = {}) {
    visualEffect = CustomVisualEffect().apply(block)
}

See the haze-blur module for a complete reference implementation.

Styling Resolution

When multiple sources of styling are provided, Haze resolves values using the following precedence:

  1. Value set directly in the effect builder (e.g., blurEffect { })
  2. Value set via the style property
  3. Value set via LocalHazeBlurStyle composition local
  4. Default value

This allows flexible composition of styles from different sources while maintaining predictable behavior.