fix(tabs): make the selected editor tab distinguishable in light and dark - #2434
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2428.
The bug
The track and the selected tab were drawn with the same material:
glassEffect(.regular, in: Capsule), both of them, inside oneGlassEffectContainer.Liquid Glass samples the backdrop beneath the whole stack, never the glass it sits on. Two identical
.regularsurfaces therefore carry no step of their own, and whatever step there is comes from the window's own content rather than from the design. Measured on the shipping build, sample database, three tabs:In light the selected tab was two levels darker than the track, which is no selection at all. And it is not a fixed weakness: across a backdrop sweep from black to white the step turns over, running +57 to -1 in light and +29 to -20 in dark, so the same tab strip can show the selection, hide it, or invert it depending on what the window is over.
The system does not stack one material on itself. A runtime probe of AppKit's own
NSTabBaron macOS 27 shows the track is anNSSubduedGlassEffectViewcarrying plainNSGlassEffectViewtabs, two different materials, measured at 236/253 in light and 83/89 in dark.NSGlassEffectViewStylepublishes onlyregularandclear, so that subdued style is out of reach.The fix
Opposing neutral tints. The track is tinted toward black and the selected tab toward white. Both sample the same backdrop and move away from it in opposite directions, so the step stops depending on what is behind the window:
NSTabBarIn the running app that lands at 78/141 in dark and 227/247 in light, against the system's 83/89 and 236/253.
Both tints are neutral on purpose:
Glass.regulardiscards hue and reads only lightness, measured, so a red track against a green selection behaves exactly like no tint at all and flips just the same. That also matches the HIG, which asks for colour on the background rather than on the text.A hairline rim on the selected capsule. macOS attenuates a glass tint in a window that is not key, measured at 134 falling to 94 for the selected tab, and the system's own bar gives up there too, its selected tab reading seven levels darker than its track in a background window. The rim is ink rather than material, so it survives that. It is also a shape rather than a colour, which is the second channel the differentiate-without-colour criteria ask for.
Checked on a running build with the window sent to the back: the rim draws, and the selected tab measures 81/105, a contrast of 1.40:1 with the sign the right way round. The system's own tab bar inverts in that state.
Increase Contrast and Reduce Transparency. The strip answered neither. Rendering it under
accessibilityHighContrastAquaandaccessibilityHighContrastDarkAquaproduced pixels identical to plain aqua and darkAqua, measured. Either setting now takes the strip off glass and onto the opaque surfaces, which carry 1.371:1 in light and 1.991:1 in dark, the wayMaterialAccessibilityalready answers both settings in six other views.The macOS 14/15 path, which was worse.
selectedTabSurfacefilled the selected capsule with.unemphasizedSelectedContentBackgroundColorwhenever the window was not in front, which is the exact constant the track uses. Both resolve opaque, so the delta was zero: a background window showed no selected tab at all, in either appearance. The shadow meant to cover for it was drawn in light alone and clipped away by the track's own 24pt capsule. The selected fill is now a tone of its own and does not step down; only the labels do, which they already did.What was not done
The issue suggests tinting the active tab's title with the app accent or the connection colour. That is not the macOS convention: no anchored Mac app does it, the HIG says to put colour on the background rather than on symbols or text, TablePlus keeps its connection colour in the status bar and never lets it signal selection, DataGrip colours backgrounds and VS Code uses a border. It also cannot work here structurally, because the strip lists one connection's tabs, so every tab would carry the same colour and none of them would stand out.
Tests
EditorTabStripChromeTestsnow pins the surface style to.solidand renders that path. This is a real change in what it covers, and worth stating plainly: a tintedglassEffectcannot be rasterised at all.cacheDisplayreturns an empty bitmap for the whole hosting view, the strip's own titles and close button included, and every measurement reads exactly zero. Untinted glass lets its content rasterise, which is why the suite worked before.So the suite now covers the surfaces that ship to macOS 14 and 15 and to anyone with either accessibility setting on, which had no coverage at all and is where the background-window bug shipped. It keeps the three existing z-order assertions and gains one that samples the selected capsule against a plain one in both appearances and both window states, which fails on the old fallback. The glass path is verified against the running app, measured and screenshotted.
One consequence to state plainly: the
GlassEffectContainerlayering that this file broke twice before now has no automated guard at all, because the path it lives on cannot be rasterised. The two assertions that covered it still run, but against the solid surfaces. It is checked by hand against the running app, in both appearances and with the window both in front and behind.EditorTabStripSurfacesTestsis new and pins the arithmetic: the two tints pull in opposite directions, both are neutral, the opaque selected fill is never the track's own colour in any of the four appearances, and either accessibility setting alone takes the strip off glass.Verification
verify.sh buildPASSverify.sh test EditorTabStripChromeTests EditorTabStripSurfacesTests EditorTabStripLayoutTests EditorTabStripAccessoryControllerTestsPASS, 34 of 34verify.sh lint0 violations on all four filesmainin a worktree before and after, to confirm the suite rework was a real change and not an environment failureBefore / After
Screenshots are attached below: the tab strip with three tabs,
Query 2selected, in both appearances. In the light "before" the selected tab is visible only by its close button.