Skip to content

Add overlay and combined dimming methods - #23

Merged
aquitaine merged 1 commit into
mainfrom
overlay-dimming
Jul 19, 2026
Merged

Add overlay and combined dimming methods#23
aquitaine merged 1 commit into
mainfrom
overlay-dimming

Conversation

@aquitaine

@aquitaine aquitaine commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Parity-map §1: overlay dimming + combined (gamma+overlay) dimming, darker than gamma alone.

  • DimmingComposer (pure, 13 tests): per-method gamma/overlay split, monotonic darkness, floor/cap invariants (never fully black — the composer caps overlay at 0.9 and gamma at 0.15, so a dim can't become an accidental blackout).
  • DimOverlayController: black click-through windows below the status-bar level (menu bar + popover never dim away), screen-capture-excluded, process-bound (no quit restore needed), reconciled on every topology change.
  • Method picker in the Dimming card; live dims re-express on switch.

314 package tests green; both app schemes build.

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>
@aquitaine
aquitaine merged commit ac70ac5 into main Jul 19, 2026
@aquitaine
aquitaine deleted the overlay-dimming branch July 19, 2026 23:13

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +560 to +562
let split = DimmingComposer.split(method: settings.dimmingMethod, level: level)
observer.setGammaDim(split.gammaLevel, for: cgID)
dimOverlay.setAlpha(split.overlayAlpha, for: cgID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +352 to +354
private var range: ClosedRange<Double> {
model.settings.dimmingMethod == .gamma ? 0.15...1 : 0...1
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@aquitaine aquitaine mentioned this pull request Jul 19, 2026
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.

2 participants