$window, $display and $metrics
The app's own window, as state: its color mode, its blur and dim, its refresh rate, and how its frames are doing.
Why these exist
A Compose screen has no way to reach its own Window. Anything that wants to
set a color mode, ask for a refresh rate or blur what is behind a dialog has to find the
Activity, keep a reference to it, and drop that reference before it leaks. Every app that
does this invents the same plumbing. These three objects are that plumbing, generated
once per app and attached to the Activity for you.
They are objects, not calls. $display and $window are substituted
wherever they appear, so they work in a click handler and a function body as well as in
markup.
$display
val mode = $display
mode.current // "default" | "wide" | "hdr" | "hdr10"
mode.available // can this display report an HDR/SDR ratio?
mode.hdrSdrRatio // 1.0 outside the HDR modes
mode.headroom // what the window asks for, as a multiple of SDR white (API 34)
mode.set("hdr") current, available and hdrSdrRatio are state. set asks the window for a mode; what you get is what current reports afterwards, which on a panel without HDR is nothing. Hide the control behind available rather than disabling it: on such a display an HDR button does
not fail, it succeeds and changes nothing.
$display is Android's window color mode and HDR headroom. It is not the
web's dynamic-range media query, which only reads the capability; this one
sets it.$window
$window.dim = 0.2f // dim what is behind the window, 0..1
$window.blurBehind = 10 // blur it, in dp; $window.blurSupported says whether it will
$window.backgroundBlur = 20 // blur the window's own background
$window.frameRate = 1f // ask for a refresh rate; 0 means no preference
$window.displayRefreshRate // what the display is actually doing, as state The writes go to the Activity's window at the moment they run. The reads are state, so
a <Text> showing displayRefreshRate follows the panel as
it switches between 60 and 120.
$metrics
val fx = $metrics
fx.frameTimeMs // average over the last sixty frames
fx.gpuTimeMs
fx.worstFrameMs
fx.dropped // frames that missed the budget, in the window
fx.jankPercent
fx.totalFrames
fx.budgetMs // 16.67 at 60 Hz, 8.33 at 120 Every field is state, so an overlay that shows them redraws as the numbers move. whitehall bench measures the same thing from outside, as a gate; this is the
app showing it to itself.
Try it
import $lib.components.ui.Button
val mode = $display
<Column class="gap-8">
<Text>{mode.current}</Text>
<Row class="gap-4">
<Button onClick={() => mode.set("default")}>sRGB</Button>
<Button onClick={() => mode.set("wide")}>P3</Button>
<Button onClick={() => mode.set("hdr")} disabled={!mode.available}>HDR</Button>
</Row>
</Column>$display is the window's color mode as state. Reading current or available in markup subscribes to it; set() asks the window for a mode.
See Also
- Theming — light and dark are a theme concern, not a window one
- $appContext — the other place Android's plumbing is done for you
- whitehall bench