Releases: Shyzkanza/KWave
Releases · Shyzkanza/KWave
0.2.0
More natural motion and a tighter battery/performance contract.
Note: the drop-in
KWaveoverload gained two parameters, which changes its binary signature.
Source-compatible (the new parameters are trailing with defaults), but consumers compiled against
0.1.0 must recompile — standard for a 0.x minor.
Added
- Crest sway: each breathing layer's crests now lean slowly side to side on top of the
amplitude breathing, so the surface rolls organically instead of only pulsing vertically. The
sway is scaled by the layer'sbreathDepth(abreathDepth = 0layer has zero sway and stays
fully static), runs slower than the breathing, and is phase-decorrelated from it per layer. - Elevation-style depth shading. Each wave now casts a diffuse, blur-like shadow on the
content behind it (background and further-back layers), like stacked translucent sheets,
and its crest is gently lit from inside the fill (the top of each wave's gradient is lifted
towardWaveColors.highlight) instead of carrying a separate highlight shape — a thin band
tracing a wide crest read as a "string". The render pass is interleaved per layer
(shadow → fill, back to front), so a shadow can never bleed over a nearer wave — the 0.1.x
two-pass design let back-layer shadows smear across the front waves, and its straight vertical
fades washed the waves instead of shading their edges.ShadowModekeeps its semantics for the
shadow (Autoby luminance,Custom.alpha= the shadow's total peak opacity,Nonedisables
it), and every layer now gets the FX, including the front-most. - Per-wave depth gradient fill. Each layer's body is now painted with a subtle vertical
gradient (its palette color at the crest, slightly darkened toward the bottom) instead of a flat
color, giving every wave internal depth. WaveColors.withBackground(...): decouples the backdrop from the wave palette. The factories
still build a coherent scene from one set of colors;withBackground(color)/
withBackground(top, bottom)/withBackground(stops)then replace only the background,
leaving the wave fills and highlight untouched.withBackground(Color.Transparent)is the
waves-only mode: the renderer skips the background pass entirely, so KWave can sit on top of
caller-provided content (locked by the newkwave_waves_onlygolden).swayparameter onWaveConfigandWaveConfig.generate: the config-wide weight of the
crest sway, coerced>= 0.1(default) is the nominal organic roll;0fdisables the sway and
restores the exact 0.1.x waveform (breathing without sway).driftparameter on the drop-inKWave: a gentle ambient horizontal travel in radians of
phase per second, parallaxed per layer byWaveLayerSpec.speed. Default0.05(a full phase
cycle ≈ 2 minutes);0fremoves the travel. Combinedrift = 0fwithWaveConfig.sway = 0fto
fully restore the in-place motion of 0.1.x.maxFpsparameter on the drop-inKWave: an optional cap on the animation update rate.
Skipped frames publish no state, so nothing is invalidated, re-drawn, or composited — a large
battery win on 120 Hz displays (24–30is usually indistinguishable for this slow motion).
<= 0(the default) updates on every display frame.
Changed
- Default drop-in motion is now breathing + crest sway + slow drift (was: strictly in-place
breathing). Both new strands are subtle and sinusoidal, and each has an off switch (drift = 0f,
WaveConfig.sway = 0f). - The stateless
KWave(config, phase, time)overload paints different pixels than 0.1.0 for
layers withbreathDepth > 0(the sway term participates in the waveform; it stays a pure
deterministic function of its inputs). Downstream screenshot goldens must be re-recorded, or pass
aWaveConfigwithsway = 0ffor the 0.1.x waveform. speedanddriftare integrated per frame instead of being multiplied by the total
elapsed time, so changing either live (a slider, an animated transition) alters the tempo from
that frame on without snapping the accumulated position. Note thatspeeddoes not scale
drift; useisPlaying = falseto freeze all motion.- Frame-driven state is read in the draw phase. A running animation now invalidates only the
draw pass; theKWavecomposable no longer recomposes on every frame. - The renderer caches all frame-invariant paint objects. The background gradient, per-layer
resolved fill colors, and per-layer shadow/highlight band brushes are rebuilt only when the
config or the canvas height changes; a steady frame allocates zeroPaths and zeroBrushes
(previously the brushes were re-created every frame). ThePathcache survives any config change
that keeps the layer count. - The waveform engine computes in double precision and the drop-in integrates elapsed time in
double precision, so the motion keeps frame-level smoothness after days of continuous runtime
(kiosk / always-on screens). AFloatseconds accumulator degraded after a few hours. WaveColorsnow implements structural equality (equals/hashCode/toString). Two factory
calls with identical inputs compare equal, so aWaveConfigrebuilt with the same values keeps
matchingrememberkeys (including the renderer's internal cache) instead of being treated as a
new configuration.
Fixed
isPlaying = falseno longer pumps frames. The frozen loop previously kept a
withFrameNanospending on every frame (only to track the clock baseline), forcing the frame
clock to keep producing frames at the display rate while visually frozen. The loop now truly
suspends (zero frames, zero rendering work) and resumes without a time jump.- Changing
speedmid-animation no longer snaps the wave. It was previously multiplied by the
total elapsed time, so a live change rescaled all accumulated history (the jump grew with
uptime); it is now integrated per frame (see Changed).
Full Changelog: 0.1.0...0.2.0
0.1.0
Initial release of KWave: animated, customizable layered wave hero backgrounds for Compose
Multiplatform.
Added
KWavedrop-in composable that owns its ownwithFrameNanosanimation loop. It is
lifecycle-aware (pauses belowSTARTED, resumes with no time jump), honors the system
reduce-motion setting (renders a single static frame when on), supports anisPlayingfreeze and
aspeedmultiplier, and reads a livephaseShiftevery recomposition for pager/scroll sync. It
randomizes its initial phase per instance so multiple instances do not synchronize.- Stateless
KWave(config, phase, time, modifier)composable: a deterministic function
of(phase, time)with no internal state, for screenshot tests and external
synchronization. WaveColorscolor strategy (theme-free; noMaterialTheme) built through factories:
gradient(top, bottom),palette(colors)(rainbow, depth-sampled per layer), andsolid(color).
Per-layer fills are palette-derived, never a hardcoded black.ShadowModesealed interface:Auto(default, per-layer black/white by luminance),
FromWave,None, andCustom(color, alpha). Controls both the depth shadow band and the
luminous highlight lip.WaveConfigwith a neutralDefaultpreset and agenerate(waveCount, crests, harmonic, spacing, amplitude, variation, colors, shadow, gradientEnd, seed)factory that auto-distributes
the static per-layer phase, alpha-by-depth, breathing, and per-layer tint.crestsis a relative
crest density (1= baseline) and its twinharmonicis the crest roughness (0= clean rounded
sine, higher = choppier).harmonic(crest roughness) param ongenerate(), the twin ofcrests(density):
0is a clean rounded sine, higher mixes in more second-harmonic weight for choppier, less regular
crests. Jittered per layer like the other generator inputs.WaveLayerSpecadvanced low-level layer spec with all values coerced into valid ranges at
construction, pluswithTint/withAlphaergonomic overrides.- Configurable
gradientEndvertical gradient fraction. - Per-layer
harmonicweight with0fproducing a pure sine wave. spacing,variation, andseedparameters onWaveConfig.generate.spacingcontrols
the vertical spread/overlap of the layers,variation(in[0, 1]) is the amount of per-layer
pseudo-random jitter that desyncs the layers, andseedmakes that jitter
deterministic (same seed ⇒ same layout; change it for a different one).- Renderer honors the caller's
Modifierverbatim (no forcedfillMaxSize), guards against
zero-size layouts, cachesPathobjects across frames, and keeps thedropLast(1)depth effect
safe at any layer count (includingN = 0andN = 1). - Kotlin Multiplatform targets:
androidTarget,iosArm64,iosSimulatorArm64,jvm. - Stable public API: regular
@Immutableclasses (notdata classes) andImmutableListlayers,
tracked by the binary-compatibility-validator with a committedapi/dump; Dokka API docs. - Compose Desktop sample app (
./gradlew :sample:run) doubling as a live visual test harness.
Changed
- Drop-in motion is now in-place breathing, not horizontal drift. The drop-in
KWaveholds the
ambientphaseconstant (phase = initialPhase + phaseShift); the only ambient motion is the
per-layer amplitude breathing (time = elapsed * speed), so the waves oscillate in place rather
than marching sideways.speedis now the breathing/bob tempo multiplier (not a drift speed), and
phaseShiftremains a live external signal for explicit horizontal translation (e.g. a pager
offset). solid()and same-colorgradient()now ramp the per-layer fill by depth (darker back →
lighter front) so monochrome/same-color waves stay visible over the same-color background, instead
of relying on alpha alone.palette()background calmed to a muted wash (rainbow stays on the wave fills). The background
is no longer the full saturated multi-stop palette; it is a muted two-stop gradient derived
(darkened) from the palette extremes, so the colorful waves stay the subject.generate()gainedgradientEnd;seedmoved to the end as an advanced param. Callers can
now set the background gradient end directly instead of rebuilding a secondWaveConfig;seedis
now the last argument and documented as advanced (leave at0unless you need a reproducible
re-roll or to pin a screenshot).phaseSpreadremoved fromgenerate(). Its effect was a barely-perceptible static crest
reshuffle, swamped in practice by the per-layer phase jitter, so the high-level factory no longer
exposes it. The low-levelWaveLayerSpec.phaseOffsetis unchanged and still lets power users
set a per-layer horizontal phase directly.
Fixed
ShadowMode.Custom.alphanow drives the rendered shadow band. It was previously ignored
(a no-op); the supplied alpha is now applied as the band's peak opacity.- Degenerate background gradient when
gradientEnd → 0.WaveConfig.gradientEndis now floored
at a small positive minimum (GRADIENT_END_MIN ≈ 0.04), so the backgroundverticalGradientnever
spans zero height (which painted a flat, broken color).
Full Changelog: https://github.com/Shyzkanza/KWave/commits/0.1.0