Skip to content

Fix a second batch of rough edges around the pickers - #340

Merged
smelfungus merged 3 commits into
masterfrom
fix/picker-rough-edges-2
Sep 16, 2026
Merged

smelfungus merged 3 commits into
masterfrom
fix/picker-rough-edges-2

Conversation

@smelfungus

Copy link
Copy Markdown
Member

The remembered hue was substituted over every neutral, including one written straight into the space it was being read back from. From a hue-200 state, updateFromHsl(HslColor.White) answered HslColor(hue = 200, saturation = 0, lightness = 1): the hue slider snapped back after being dragged to its left end, and raising saturation gave #00AAFF instead of red. Only a converted view has a hue missing to fill in, which is what the comment beside it already claimed.

A picker taking a value and a callback kept a colour the caller had refused. The write-in was keyed on the caller's value, and a caller who declines a change β€” or rounds it back to the one they already hold, which is what snapping to steps does β€” leaves that value untouched, so the effect never ran again and the two ends stayed apart. Reporting out now re-arms the write-in.

The dialog's slider slots took no parameters while the dialog built the state itself, so a replacement had nothing to read or write and the sliders still could not be localized. They are handed the state. The dialog had no tests anywhere in the repo; it has some now.

Hex parsing defaulted to Android's alpha-first ordering, so #F00C β€” a stylesheet's red at 80% β€” came back as an opaque navy rather than null. Four and eight digits now need the ordering named. Formatting still writes #AARRGGBB, so its output needs HexAlpha.First to read back: matching the two by dropping the alpha on the way out instead would lose the channel in silence, which is the failure this default exists to avoid.

The planes answered to touch and mouse only, leaving saturation and lightness unreachable in a layout of a plane and a hue slider to anyone without a pointer. They take focus, move on the arrow keys by a percent and by ten with shift held, and offer a screen reader one named action per direction, since a surface with two degrees of freedom has no single adjustable value to expose.

The hex default is the only one of these that changes released behaviour: 1.1.1 read eight digits alpha-first with no parameter to say otherwise, so "#FF000080".toRgbColorOrNull() now returns null where it returned a colour. No signature changes, so apiCheck cannot flag it β€” it needs the release notes. The dialog slots never shipped.

The remembered hue was substituted over every neutral, including one written straight into the space it was being read back from. From a hue-200 state, updateFromHsl(HslColor.White) answered HslColor(hue = 200, saturation = 0, lightness = 1): the hue slider snapped back after being dragged to its left end, and raising saturation gave #00AAFF instead of red. Only a converted view has a hue missing to fill in, which is what the comment beside it already claimed.

A picker taking a value and a callback kept a colour the caller had refused. The write-in was keyed on the caller's value, and a caller who declines a change β€” or rounds it back to the one they already hold, which is what snapping to steps does β€” leaves that value untouched, so the effect never ran again and the two ends stayed apart. Reporting out now re-arms the write-in.

The dialog's slider slots took no parameters while the dialog built the state itself, so a replacement had nothing to read or write and the sliders still could not be localized. They are handed the state. The dialog had no tests anywhere in the repo; it has some now.

Hex parsing defaulted to Android's alpha-first ordering, so #F00C β€” a stylesheet's red at 80% β€” came back as an opaque navy rather than null. Four and eight digits now need the ordering named. Formatting still writes #AARRGGBB, so its output needs HexAlpha.First to read back: matching the two by dropping the alpha on the way out instead would lose the channel in silence, which is the failure this default exists to avoid.

The planes answered to touch and mouse only, leaving saturation and lightness unreachable in a layout of a plane and a hue slider to anyone without a pointer. They take focus, move on the arrow keys by a percent and by ten with shift held, and offer a screen reader one named action per direction, since a surface with two degrees of freedom has no single adjustable value to expose.

The hex default is the only one of these that changes released behaviour: 1.1.1 read eight digits alpha-first with no parameter to say otherwise, so "#FF000080".toRgbColorOrNull() now returns null where it returned a colour. No signature changes, so apiCheck cannot flag it β€” it needs the release notes. The dialog slots never shipped.
The write-in that stopped a picker showing a refused colour ran after every report, so a caller whose own value lands late β€” debounced, written by a background coroutine, confirmed by a store β€” had their stale colour put back under a moving finger every frame. Over a three-step drag the picker never left where the drag started. The correction now waits for the gesture to end, which still catches a rejection and costs a slow caller one visible correction on the lift rather than a thumb that will not move. The callback is documented as expected to update the value synchronously.

The hex parsers lose their default. Defaulting to HexAlpha.None turned a wrong colour into a null, but silently: an app that stored colours with 1.1.1's formatter finds all of them parsing to null, or throwing through toRgbColor, with nothing failing at compile time to say why β€” toHexString() gives #FF3380CC, and reading that back needs HexAlpha.First. Requiring the argument makes the upgrade a compile error, and dropping the $default synthetics puts it in front of apiCheck as well.

OkLCh reported a hue for every grey. The sRGB-to-Oklab matrices leave a and b around 1e-8 rather than at zero, so chroma was never exactly zero, the neutral check never fired, and every grey but black answered 89.876 degrees. Chroma under 1e-6 is now reported as zero, which is what lets the remembered hue through. This predates the planes and the state work; nothing in the library draws OkLCh, so it reached only callers using the conversion directly.

A plane showed nothing when it took focus. The default indicator gains a second ring, and a replacement can do the same: the InteractionSource it already receives carries focus as well as drag.

A plane also kept arrow presses it could not use. At an edge the press moved nothing and was still consumed, so on a device driven by a D-pad alone focus could never leave it. A press that would change neither value is passed on.

PlaneActionLabels is no longer a data class. componentN and copy are part of the ABI, so a fifth direction would have broken code already compiled against four, which is why the eight colour models write their own equals instead.
Nothing is written into the picker while a gesture is running. Waiting for the gesture to end before re-arming the write-in covered the caller who holds still and answers afterwards, but not the one who answers during the drag a frame or more behind it: their value changing was itself a key on the effect, so each late answer landed under the finger and pulled the thumb back to a colour it had already passed. Guarding the write itself covers both arrivals. The test that stood for a slow caller only queued values and applied them after the lift, so it modelled the debounced case and nothing else; the one beside it now delivers a value mid-drag.

The formatter loses its default too. Requiring the ordering only when reading left toHexString() still writing #AARRGGBB unasked, which is the same silent wrong colour one step earlier: pasted into a stylesheet it is a different colour, and read back with HexAlpha.Last it is a different colour again. Naming it at both ends puts the pair on one screen, and the round trip can no longer be written without saying what it means.

The focus ring is drawn only while the input mode is keyboard. Pressing the surface takes focus so the arrow keys carry on from where the finger left off, which also meant a touch user was left with a ring marking an affordance they have no way to use, until focus moved somewhere else. A platform with no touch reports keyboard throughout, so nothing changes on the desktop.
@smelfungus
smelfungus merged commit 04d7c49 into master Sep 16, 2026
2 checks passed
@smelfungus
smelfungus deleted the fix/picker-rough-edges-2 branch September 16, 2026 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant