Every background job reported into a window property of its own — library-thumbs-done, library-pin-total, library-syncing — which only the grid ever read. A pin download that outlived the view it was started from drew nothing at all once the user opened an image, and there was no answer anywhere to "what is this busy with", because the answer was spread across eight properties nothing collected. They report to one register now (ui/dr-ui/src/activity.rs). It publishes an aggregate, which draws a three-pixel bar across the top of the shell in every view, and a row per job, which the settings page lists: scans, thumbnail batches, pin and open downloads, sidecar uploads, the sync and the trash. Failures stay on the list until they are cleared; routine successes do not, or a scroll would bury them. The handle removes a still-running job when it drops, so a worker that dies mid-transfer takes its row with it rather than leaving the bar sweeping for the rest of the session. Also carries in-flight work from a parallel session — the drawn icon set and the dr-pipeline ops split. dr-pipeline's build script does not compile at this commit; ui/dr-ui does, with clippy clean and its tests passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
61 lines
1.8 KiB
YAML
61 lines
1.8 KiB
YAML
id: saturation
|
|
label: op.saturation
|
|
order: 90
|
|
|
|
doc: |
|
|
Saturation — every colour's distance from grey, scaled equally.
|
|
|
|
The blunt instrument beside [`vibrance`](vibrance.yaml). Both are offered
|
|
because they fail differently: this one is predictable and even, which is
|
|
what a landscape wants, and ruinous on faces, which is what vibrance is for.
|
|
|
|
placement: |
|
|
Last of the per-colour controls, so it has the final word if both it and
|
|
vibrance are in play.
|
|
|
|
params:
|
|
saturation:
|
|
label: param.saturation
|
|
kind: amount
|
|
|
|
uniforms:
|
|
factor:
|
|
value: max(1 + saturation / 100, 0)
|
|
doc: |
|
|
-100 reaches exactly monochrome; +100 doubles the distance from grey.
|
|
The floor at zero matters: a negative factor would push a colour past
|
|
grey into its complement, inverting hues.
|
|
|
|
helpers: [luminance, tone_position, apply_tone_gain]
|
|
|
|
wgsl: |
|
|
// Interpolate away from the luminance-preserving grey. A factor of 0 is
|
|
// monochrome, 1 is unchanged, above 1 is more saturated.
|
|
let luma = luminance(c);
|
|
c = mix(vec3<f32>(luma), c, factor);
|
|
c = max(c, vec3<f32>(0.0));
|
|
|
|
tests:
|
|
- name: it_starts_unchanged
|
|
expect: { factor: 1.0 }
|
|
expect_active: false
|
|
|
|
- name: full_negative_saturation_reaches_monochrome
|
|
why: |
|
|
The property that makes -100 meaningful: it must land exactly on grey,
|
|
not merely near it.
|
|
set: { saturation: -100 }
|
|
expect: { factor: 0.0 }
|
|
|
|
- name: positive_saturation_increases_the_factor
|
|
set: { saturation: 100 }
|
|
expect: { factor: 2.0 }
|
|
|
|
- name: the_factor_never_goes_negative
|
|
why: |
|
|
A negative factor pushes a colour past grey into its complement, which
|
|
inverts hues rather than desaturating them. The clamp is what makes the
|
|
bottom of the slider's travel monochrome instead of a solarised image.
|
|
set: { saturation: -100 }
|
|
expect_range: { factor: [0.0, 2.0] }
|