Expand description
Filter wire types, built-in filters, and the filter registry: the
layer-based filter style chain.
This module owns the filter wire format and registry the way
crate::canvas/crate::animations own theirs: protocol/
reconcile/ui_map reference these types by module path, never the
reverse. On the wire a filter is one {"name", "params"} object or an
ordered array of them; params stays an untyped JSON map at this layer —
typed validation happens later against the registry, not during decode.
Decoding follows the protocol’s warn-don’t-abort convention (see
Length’s custom Deserialize): a malformed
value emits a decode_warn and the whole chain degrades to empty, never
failing the containing Style’s deserialization.
The typed layer below the wire — ReactFilter, the built-in param
structs, and FilterRegistry — turns a FilterUse into
ResolvedFilterPasses: deserialize the raw params (strict:
deny-unknown-fields), pack them into a Vec4 uniform array with a
ParamSlot layout (no-straddle rule: a slot never crosses a Vec4
boundary), and pick the pass shader. The thirteen built-ins register via
register_builtin_filters; custom filters are #[react_filter] structs
registered with add_react_filter.
The module also owns the runtime write path of a chain.
resolve_chains turns a promoted root’s FilterInput into a
ResolvedFilterChain component — resolving each entry against the
registry (invalid entries skip with a filterParams warning), rewriting
Length slots to physical px, stamping ResolvedFilterPass::wire_index,
and summing the chain outset. The interpolation primitives
(lerp_packed_params, lerp_angle) blend packed param arrays
layout-aware for the easing paths. Exactly three systems write
ResolvedFilterChain and bump its version counter: the resolver’s
snap, the transition filter channel’s whole-value ease (transition.rs,
planned by [plan_filter_ease] with identity-padding for chain
extensions), and the animation per-param filter[<i>].<param> bindings
(animations) — see ResolvedFilterChain::version for the writer
registry and precedence.
File map: wire (the wire format + warn-don’t-abort decode), params
(packing layout, caps, param value types, interpolation), registry (the
ReactFilter trait, resolved passes, the registry), builtin (the
thirteen built-ins), morph (the morphFilter style’s wire type + resolver
instance + runtime state), transition (whole-value filter transition
planning),
resolve (the chain resolve system). Submodules are private; everything
is re-exported here, so crate::filters::X is the one path.
Structs§
- Backdrop
Input - The wire
backdropFilterchain of a node, mirrored off the applied style bycrate::ui_map::apply_style_masked’s BACKDROP arm — present iff the style carries a non-empty chain. Same contract asFilterInput: the style apply owns writes (the applied style may be hover/press/focus-merged), the resolver only reads. - Bloom
Params bloom: glow where bright areas bleed light — a bright-pass thresholded atthreshold, blurred byradius, and added back onto the original scaled byintensity.{ name: "bloom" }with no params is a visible glow (shorthand-default convention); the true identity isintensity: 0.- Blur
Params blur: separable Gaussian blur.radiusdefaults to0— CSSblur()with the value omitted means0(identity).- Brightness
Params brightness:amountdefaults to1.0— CSSbrightness()with the value omitted means1(identity).- Chromatic
Aberration Params chromaticAberration: directional color fringing — the R channel’s image shiftsoffsetpx alongangle(degrees, clockwise from +X in screen space; bare number = degrees,"0.25turn"etc. accepted), B the same distance opposite, G stays put. The split is uniform across the layer.{ name: "chromaticAberration" }with no params is visible fringing (shorthand-default convention); the true identity isoffset: 0.- Contrast
Params contrast:amountdefaults to1.0— CSScontrast()with the value omitted means1(identity).- Crossfade
Params crossfade: a noise-staggered two-input blend. A smooth fbm value-noise field gives each pixel a stagger offset (n * spread) so blob-shaped regions cross-dissolve earlier than others; each pixel then fades linearly over asoftness-wide window of the progress range.spread: 0is the plain uniformmix(from, to, progress)(bit-exact — the shader fast-paths it), and noise is ON by default.- Filter
Chain - An ordered filter chain. Decodes from a single
{"name", "params"}object (a 1-element chain) or an array of them (applied in order); any malformed entry — or non-object/array garbage — warns and degrades the whole value to an empty chain. - Filter
Color - A color filter parameter: linear (shader-ready) straight-alpha RGBA.
- Filter
Input - The wire
filterchain of a node, mirrored off the applied style by the apply path (crate::ui_map::apply_style_masked’s FILTER arm) — present iff the style carries a non-empty chain. The thin input side of theresolve_chainssystem, mirroring thecrate::transition::TransitionInputpattern: the style apply owns writes, the resolver only reads. The applied style may be a hover/press/focus- merged one (the field isoverlay), so an interaction flip re-stamps the merged chain here. - Filter
Registration - One registered filter: everything the chain resolver needs, baked into
AssetServer-free fn pointers at registration time (shader loading stays lazy — the pointers receive the&AssetServer). - Filter
Registry - Known filters, keyed by wire name. Populated by
register_builtin_filters; consumed byresolve_chains. - Filter
Use - One filter invocation in a chain: a registry name plus its raw, untyped
parameter map (empty when the wire object has no
paramsorparamsisnull). - Gradient
MapParams gradientMap: recolor the layer’s pixels with a multi-stop linear gradient, keeping alpha — gradient text (wrap the<text>in a<node>), gradient-tinted icons.anglefollowsbackgroundGradient’s convention (bare number = degrees,0= to top, clockwise); the gradient line spans the node’s border box (aspect-correct CSS math — the outset ring of a chained blur/outline does NOT stretch it, via the prelude’scontent_uv).amountmixes the original color toward the gradient (a stop’s alpha scales the mix locally; source alpha is always kept); the true identity isamount: 0. Gradient interpolation is linear-RGB (the packed space — a v1 divergence frombackgroundGradient’s oklab default).- Gradient
MapStop - One gradient stop: a CSS color and an optional position as a fraction
0..1along the gradient line. - Grayscale
Params grayscale:amountdefaults to1.0— CSSgrayscale()with the value omitted means1(full effect; identity is0), so a bare{name:"grayscale"}applies the effect.- HueRotate
Params hueRotate:angledefaults to0deg— CSShue-rotate()with the value omitted means0deg(identity). Packs radians atparams[1].zof the shared color-matrix packing.- Invert
Params invert:amountdefaults to1.0— CSSinvert()with the value omitted means1(full effect; identity is0).- Linear
Wipe Params linearWipe: the “to” image sweeps in alongangle(degrees, clockwise from +X in screen space, like every angle in the system; 0 wipes left-to-right, 90 top-to-bottom), with asoftness-px eased band around the wipe edge. The sweep is extended by the band at both ends so progress 0 is fully “from” and progress 1 exactly the live content (the identity contract).- Morph
Filter - The decoded
morphFilterstyle value: the morph identitykey(a string or number — a change is what triggers the morph) plus the two-input filter to blend with, as a regular registryFilterUse. - Morph
Input - The wire
morphFilterof a node, mirrored off the applied style bycrate::ui_map::apply_style_masked’s MORPH arm — present iff the style carries the field. Same contract asFilterInput: the style apply owns writes, the resolver only reads. Thekeyrides outside the chain: the resolver never looks at it (a key-only change re-resolves to identical output, version-quiet) — retarget detection is the morph transition channel’s job. - Morph
State - The live state of a node’s morph transition, written by the morph channel
in
crate::transition(retarget on key change, per-frame progress) and read by render extraction (the freeze request + blend pass). Inserted on first activation; removed on demotion. - Outline
Params outline: paint acolorring ofwidthpx around the content’s alpha silhouette, UNDER the content (source-over) — text outlines (wrap the<text>in a<node>), sticker-style icon rings.softnessfeathers the ring’s outer edge over that many extra px, doubling as a glow. The outline follows whatever the chain has produced so far:[gradientMap, outline]outlines the recolored glyphs,[blur, outline]the blurred silhouette.{ name: "outline" }is a crisp 2px black outline (shorthand-default convention); the true identity iswidth: 0, softness: 0.- Param
Slot - Where one named parameter lands in a pass’s packed
Vec4array:vecis theVec4index,compthe starting component within it,lenhow many consecutive components the param spans. - Pixelize
Params pixelize: both images sample the center of a shared mosaic cell whose size peaks mid-transition, over a plain progress crossfade. Upstream uniforms map 1:1 —squaresMin(ivec2, the cell count when the mosaic is coarsest) andsteps(int, quantizes the cell-size ramp;<= 0disables the stepping for a continuous ramp).- Resolved
Backdrop Chain - A node’s fully resolved
backdropFilterchain — a newtype overResolvedFilterChainso extract, transitions, animations, and devtools reuse the inner type via.0. Attached to promoted roots by the backdropresolve_chainsinstance; absent when the chain has no valid entries. Removed on demotion byevaluate_layer_promotions’s cleanup (like the content chain). - Resolved
Filter Chain - A node’s fully resolved
filterchain, attached to promoted layer roots byresolve_chains. Absent on a promoted root whose chain has no valid entries (pure capture/composite — no filter machinery). - Resolved
Filter Pass - One resolved render pass of a filter chain.
- Resolved
Morph Chain - A node’s fully resolved morph filter — a newtype over
ResolvedFilterChain(always exactly one pass, perMorphInput::validate_entry) so extract, animations, and devtools reuse the inner type via.0. Attached to promoted roots by the morphresolve_chainsinstance; absent when the entry is invalid (the morph degrades to a snap). Removed on demotion byevaluate_layer_promotions’s cleanup. - Saturate
Params saturate:amountdefaults to1.0— CSSsaturate()with the value omitted means1(identity).- Sepia
Params sepia:amountdefaults to1.0— CSSsepia()with the value omitted means1(full effect; identity is0).- Shadow
Params shadow: a CSS-drop-shadow-style shadow — the content’s alpha silhouette, tintedcolor, shifted byoffsetX/offsetYpx (positive = right/down), Gaussian-blurred byspread, composited UNDER the content.{ name: "shadow" }with no params is a soft black shadow below (shorthand-default convention); the true identity iscolor: "transparent".
Constants§
- MAX_
CHAIN_ LEN - Longest decodable chain.
ResolvedFilterPass::wire_indexis au8, so only entry indices0..=u8::MAXare addressable; a longer chain is nonsense input and degrades whole-value like any other malformed chain. - MAX_
FILTER_ OUTSET_ PX - Defensive cap on a chain’s summed outset, physical px per side. Far beyond
any sane blur (quality is bounded well before this — see
blur.wgsl’s MAX_HALF note), it bounds the inflation math:2 * outsetadds at most 2048 texels to the capture, leaving real headroom under wgpu’s default 8192max_texture_dimension_2dfor the content itself. (Texture-limit safety proper is the allocator’s concern, not this cap’s.) - MAX_
FILTER_ PARAM_ VECS - Cap on the packed
Vec4array per pass — the fixed-size uniform array the filter shaders declare. - MAX_
GRADIENT_ STOPS - The most stops one
gradientMapuse can carry — the packing budget: 6 color vec4s + 6 positions + angle + amount is exactlyMAX_FILTER_PARAM_VECS. - MORPH_
MAX_ USER_ PARAM_ VECS - User params of a morph filter may occupy at most this many
Vec4slots: the last two ofMAX_FILTER_PARAM_VECS(= 8) are engine-reserved on a morph pass —params[7].xcarries the eased progress (see the prelude’smorph_progress;params[6]is reserved-unused spare).
Traits§
- Chain
Input - Input side of one
resolve_chainsinstance: which wire-chain component feeds it, its diag kinds, and per-instance semantics. Implemented byFilterInput(the contentfilterchain) andBackdropInput(backdropFilter). - React
Filter - A typed, named filter: how its params deserialize (strict — built-ins use
#[serde(deny_unknown_fields)]), pack into shader uniforms, and resolve into render passes. - React
Morph Filter - Marker for two-input morph filters (
morphFilternames). Implemented by#[react_morph_filter](and the built-in morphs); the bound onadd_react_morph_filtergives compile-time guidance — registration truth ridesReactFilter::IS_MORPH. - Resolved
Chain - Output side of one
resolve_chainsinstance — a component wrapping (or being) aResolvedFilterChain. The newtype projection keeps every downstream consumer (extract, transitions, animations, devtools) on the one inner type.
Functions§
- length_
logical_ px - A
Lengthfilter param’s logical-px value. OnlyPxhas a fixed logical size here — percent/viewport units have no basis for a filter param — so any other unit is rejected with a message naming it, instead of silently resolving to0.0. Bare wire numbers decode asPxand stay accepted. - lerp_
angle - Shortest-arc interpolation between two angles in radians, for packed
ValueKind::Anglefilter params. - lerp_
packed_ params - Interpolate two packed param arrays of the same layout, slot-by-slot: the layout (not the raw components) decides how each param blends.
- quantize_
outset - Quantize a physical-px outset up to the next multiple of 16 so an animated radius grows a layer texture in coarse steps instead of reallocating every frame.
- register_
builtin_ filters - Register the sixteen built-in filters. Called by
ReactUiPlugin::build. DeliberatelyAssetServer-free: shader loads happen lazily inside each entry’sresolve. - resolve_
chains - Turn each promoted root’s wire chain input
Iinto a packed resolved chainR. Two instances run inUpdateafter the interaction restyle (the last input writer this frame) and before the transition/animation appliers — both write onto the resolved chain: the content instance (FilterInput→ResolvedFilterChain, thefilterstyle) and the backdrop instance (BackdropInput→ResolvedBackdropChain, thebackdropFilterstyle — seecrate::filters::backdrop). - resolve_
single_ pass - The default single-pass resolve body:
ReactFilter::pack+ the param-vec cap check + one pass taggedwire_index: 0.