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
DrawScopereceiver.
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
RenderNodewith 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:
- Value set directly in the effect builder (e.g.,
blurEffect { }) - Value set via the
styleproperty - Value set via
LocalHazeBlurStylecomposition local - Default value
This allows flexible composition of styles from different sources while maintaining predictable behavior.