Add overlay and combined dimming methods - #23
Conversation
DimmingComposer (TopologyCore, pure, 13 tests) splits one dim level into gamma + overlay components per method: gamma (unchanged, 0.15 floor), overlay (black click-through window, capped at 0.9 opacity), combined (gamma absorbs the first 55% of the range, overlay stacks past the floor — darker than either alone, never fully black). DimOverlayController owns the per-display overlay windows: borderless, mouse-transparent, all Spaces, below the status bar so the menu bar and popover always stay reachable, excluded from screen capture, and process-bound so quitting can never leave a screen darkened. Overlays re-fit on topology changes, drop with departed displays, clear on Black Out (gamma zero is already black) and when hardware brightness returns. Dimming card gains a Gamma/Overlay/Combined segmented picker (persisted dimmingMethod setting, default gamma); switching re-expresses live dims through the new method. Slider floor drops to 0 for the overlay methods. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38ff73b7f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let split = DimmingComposer.split(method: settings.dimmingMethod, level: level) | ||
| observer.setGammaDim(split.gammaLevel, for: cgID) | ||
| dimOverlay.setAlpha(split.overlayAlpha, for: cgID) |
There was a problem hiding this comment.
Route software brightness through the selected dimming method
On a display resolved to BrightnessMethod.software—including every public-API-only build and DDC-less external—the selected method is used only when this helper is called; setBrightness still writes gamma directly at lines 869–872. After an overlay dim exists, moving the menu or media-key brightness to 100% leaves that overlay visible while storing softwareDim == 1, so the UI reports full brightness and later method switches skip the stale overlay. Route the software-brightness branch through applyDim as well.
Useful? React with 👍 / 👎.
| if !inputSource.keys.allSatisfy(ids.contains) { inputSource = inputSource.filter { ids.contains($0.key) } } | ||
| if !colorProfileName.keys.allSatisfy(ids.contains) { colorProfileName = colorProfileName.filter { ids.contains($0.key) } } | ||
| if !softwareDim.keys.allSatisfy(ids.contains) { softwareDim = softwareDim.filter { ids.contains($0.key) } } | ||
| dimOverlay.reconcile() // drop overlays for departed displays, re-fit after mode/layout changes |
There was a problem hiding this comment.
Reapply combined dimming after display reconfiguration
When a resolution change or wake resets Core Graphics transfer tables—as this file already documents for gamma—refresh reaches this line but only refits existing overlay windows. An active .combined dim therefore loses its gamma component while softwareDim and the slider remain unchanged; levels in the gamma-only portion can jump all the way back to full brightness. Reapply each live split through applyDim after the topology settles, rather than reconciling only overlay frames.
Useful? React with 👍 / 👎.
| private var range: ClosedRange<Double> { | ||
| model.settings.dimmingMethod == .gamma ? 0.15...1 : 0...1 | ||
| } |
There was a problem hiding this comment.
Keep the gamma slider binding within its range
If a user dims below 15% with Overlay or Combined and then selects Gamma, setDimmingMethod preserves the stored value (for example, 0) while this range becomes 0.15...1. The composer applies an actual gamma level of 15%, but the binding remains outside the slider's range and the readout still reports 0%, causing inconsistent state and possible snapping on the next interaction. Clamp or normalize the displayed/bound value when entering Gamma, or keep a range that includes the retained value.
Useful? React with 👍 / 👎.
Parity-map §1: overlay dimming + combined (gamma+overlay) dimming, darker than gamma alone.
314 package tests green; both app schemes build.