Skip to content

[PowerDisplay] Debounced + wheel-scrollable brightness/contrast/volume sliders#47756

Open
niels9001 wants to merge 3 commits intomainfrom
niels9001/powerdisplay-slider-debounce
Open

[PowerDisplay] Debounced + wheel-scrollable brightness/contrast/volume sliders#47756
niels9001 wants to merge 3 commits intomainfrom
niels9001/powerdisplay-slider-debounce

Conversation

@niels9001
Copy link
Copy Markdown
Collaborator

@niels9001 niels9001 commented May 8, 2026

Summary of the Pull Request

The brightness / contrast / volume sliders in the PowerDisplay flyout previously committed to hardware (DDC/CI) only on PointerCaptureLost plus arrow-key KeyUp. That doesn't work reliably on touchscreens — tap-on-track issues no pointer capture, and the Slider / Thumb doesn't always bubble PointerCaptureLost on touch — so the value the user dialed in never reached the monitor.

This PR replaces that wiring with the simpler approach we actually wanted: a TwoWay Value binding plus a debounced commit in the view-model. ValueChanged runs the setter on every move, but a 200 ms DispatcherQueueTimer (restarted on each change) collapses the entire interaction into a single DDC/CI write once the user stops sliding. Mouse, touch, tap-on-track, and held arrow keys all use the same code path.

It also adds a small SliderExtensions helper (Toolkit-style attached properties) so the brightness / contrast / volume sliders can be adjusted by mouse-wheel scroll. The wheel event is marked handled so the parent ScrollViewer does not also scroll, and the existing 200 ms debounce coalesces wheel input into a single DDC/CI write.

PR Checklist

  • Closes: Power Display Slide Control with Touch Screen Monitor #47724
  • Communication: I've discussed this with core contributors already.
  • Tests: Manual smoke; no automated coverage exists for the slider flyout.
  • Localization: No new end-user-facing strings.
  • Dev docs: N/A
  • New binaries: N/A
  • Documentation updated: N/A

Detailed Description of the Pull Request / Additional comments

Debounced commit

  • MonitorViewModel.Brightness/Contrast/Volume setters now: assign field → OnPropertyChanged()ScheduleCommit(...) which (re)starts a per-metric DispatcherQueueTimer. On Tick the closure calls SetBrightnessAsync(_brightness) (etc.), reading the latest field value so intermediate drag values are coalesced into one hardware write.
  • SetBrightnessAsync / SetContrastAsync / SetVolumeAsync (used by hotkeys, profile load, hardware refresh) keep their immediate-apply behavior — they bypass the public setters, so a TwoWay source-update from one of those paths does not reschedule a commit.
  • MonitorViewModel.Dispose() stops the three timers. Pending writes are intentionally dropped: Dispose only runs when the monitor is gone (unplug / refresh) or the app is shutting down, in which case a hardware write would race a half-torn-down MonitorManager.
  • AppConstants.UI.SliderCommitDebounceMs = 200 is the single source of truth for the delay.
  • Removed the six Handle{Brightness|Contrast|Volume}{PointerCaptureLost|KeyUp} methods and the now-unused IsArrowKey helper.
  • using DispatcherQueueTimer = Microsoft.UI.Dispatching.DispatcherQueueTimer; alias avoids ambiguity with Windows.System.DispatcherQueueTimer.

Wheel-scrollable sliders

  • New PowerDisplay.Helpers.SliderExtensions — a Toolkit-style public static class <Control>Extensions with two attached properties:
    • SliderExtensions.IsMouseWheelEnabled (bool) — opt in to wheel input.
    • SliderExtensions.MouseWheelChange (double, default NaN → falls back to the slider's own Slider.SmallChange) — per-notch delta.
  • Handler computes notches = MouseWheelDelta / 120, clamps to [Minimum, Maximum], sets Value, and marks e.Handled = true so the enclosing MainScrollViewer doesn't also scroll.
  • Wired on the brightness, contrast, and volume sliders with MouseWheelChange="5" (20 notches for a full sweep).
  • Body has zero PowerDisplay-specific dependencies (only Microsoft.UI.Xaml.*) so the helper is straightforward to lift into the WinUI Community Toolkit later.

Validation Steps Performed

  • tools/build/build.cmd against src/modules/powerdisplay/PowerDisplay/PowerDisplay.csproj — exit code 0.
  • Manual smoke on a touchscreen device:
    • Touch drag thumb 50 → 80, lift finger → monitor commits once at 80.
    • Tap on the track at 25 % → monitor commits once at 25 %.
  • Manual smoke with mouse:
    • Drag 50 → 80, release → exactly one DDC/CI write at 80.
  • Manual smoke with keyboard:
    • Hold Right arrow on the brightness slider → repeated nudges collapse into a single write 200 ms after key release.
  • Manual smoke with mouse wheel:
    • Hover brightness slider, scroll wheel → value moves by 5 per notch, monitor commits once after scrolling stops.
    • Wheel over a slider does not scroll the flyout's MainScrollViewer; wheel outside the sliders still scrolls.
    • Wheel past Minimum / Maximum clamps at the boundary.
  • Hotkey-driven brightness change still applies immediately (regression check on the SetBrightnessAsync path).
  • DisplayChangeWatcher-driven hardware refresh does not schedule a spurious commit.

…ut handling

Replaces the PointerCaptureLost + arrow-KeyUp wiring on the brightness, contrast, and volume sliders with a TwoWay binding plus a per-metric DispatcherQueueTimer debounce in MonitorViewModel. ValueChanged drives the setter; ScheduleCommit (re)starts a 200 ms timer (AppConstants.UI.SliderCommitDebounceMs) that fires the DDC/CI write once the user stops moving, coalescing intermediate values into a single hardware update.

PointerCaptureLost was unreliable on touch (tap-on-track issues no capture, and Slider/Thumb sometimes don't bubble the event), leaving the value uncommitted. The debounce works identically across mouse drag, touch drag, tap-on-track, and held arrow keys.

External / programmatic apply paths (SetBrightnessAsync etc., used by hotkeys, profile load, and hardware refresh) bypass the public setters and remain immediate. Pending debounced writes are dropped in Dispose to avoid racing a half-torn-down MonitorManager.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two attached properties on Slider:

- helpers:SliderExtensions.IsMouseWheelEnabled - opt in to wheel input.

- helpers:SliderExtensions.MouseWheelChange - per-notch delta (defaults to

  the slider's own SmallChange when unset).

Wired on the brightness, contrast, and volume sliders with a step of 5.

PointerWheelChanged is marked handled so the parent ScrollViewer does not

also scroll. Combined with the existing 200 ms debounce, wheel input

coalesces into a single DDC/CI write.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@niels9001 niels9001 changed the title [PowerDisplay] Debounce slider commits to support touch and unify input handling [PowerDisplay] Debounced + wheel-scrollable brightness/contrast/volume sliders May 8, 2026
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Raiwulf
Copy link
Copy Markdown

Raiwulf commented May 8, 2026

is it possible to include controls for switching display signals per-monitor? a dropdown to select HDMI1-2/DP1-2 and other inputs from this tool would be very useful. That way we can ignore hardware buttons

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.

Power Display Slide Control with Touch Screen Monitor

2 participants