From 26a1900d7e7a9e0b737c5a1f64ffde84e4809016 Mon Sep 17 00:00:00 2001 From: Karan Shah <42066323+karanshah229@users.noreply.github.com> Date: Sat, 18 Apr 2026 15:37:16 +0530 Subject: [PATCH] feat: Add numerical input and arrow key support for sliders - Converted menu bar percentage labels into editable text fields. - Implemented `NSTextFieldDelegate` to handle manual typing with bound clamping (0-100). - Handled invalid string parsing with graceful fallbacks to the current slider state. - Added a native rounded bezel effect when the field is actively focused. - Overrode `control(_:textView:doCommandBy:)` to capture Up/Down arrow presses for live value manipulation. - Added a `Shift` modifier for arrow keys to toggle between 1% and 10% step increments. - Ensured all manual inputs immediately sync physical hardware brightness, volume, and contrast via DDC. --- MonitorControl/Support/SliderHandler.swift | 49 +++++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/MonitorControl/Support/SliderHandler.swift b/MonitorControl/Support/SliderHandler.swift index 1d7d20e..cec290b 100644 --- a/MonitorControl/Support/SliderHandler.swift +++ b/MonitorControl/Support/SliderHandler.swift @@ -3,7 +3,7 @@ import Cocoa import os.log -class SliderHandler { +class SliderHandler: NSObject, NSTextFieldDelegate { var slider: MCSlider? var view: NSView? var percentageBox: NSTextField? @@ -212,6 +212,7 @@ class SliderHandler { public init(display: Display?, command: Command, title: String = "", position _: Int = 0) { self.command = command self.title = title + super.init() let slider = SliderHandler.MCSlider(value: 0, minValue: 0, maxValue: 1, target: self, action: #selector(SliderHandler.valueChanged)) let showPercent = prefs.bool(forKey: PrefKey.enableSliderPercent.rawValue) slider.isEnabled = true @@ -277,11 +278,12 @@ class SliderHandler { func setupPercentageBox(_ percentageBox: NSTextField) { percentageBox.font = NSFont.systemFont(ofSize: 12) - percentageBox.isEditable = false + percentageBox.isEditable = true percentageBox.isBordered = false percentageBox.drawsBackground = false percentageBox.alignment = .right percentageBox.alphaValue = 0.7 + percentageBox.delegate = self } func valueChangedOtherDisplay(otherDisplay: OtherDisplay, value: Float) { @@ -383,4 +385,47 @@ class SliderHandler { } } } + + // MARK: - NSTextFieldDelegate + + func controlTextDidBeginEditing(_: Notification) { + self.percentageBox?.isBordered = true + self.percentageBox?.alphaValue = 1.0 + } + + func controlTextDidEndEditing(_ obj: Notification) { + self.percentageBox?.isBordered = false + self.percentageBox?.alphaValue = 0.7 + guard let textField = obj.object as? NSTextField else { return } + + let stringValue = textField.stringValue.replacingOccurrences(of: "%", with: "").trimmingCharacters(in: .whitespaces) + if let value = Float(stringValue), let slider = self.slider { + slider.floatValue = max(0, min(100, value)) / 100 + self.valueChanged(slider: slider) + } else if let slider = self.slider { + self.setValue(slider.floatValue) + } + } + + func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { + guard commandSelector == #selector(NSResponder.moveUp(_:)) || commandSelector == #selector(NSResponder.moveDown(_:)) else { + return false + } + + let stringValue = textView.string.replacingOccurrences(of: "%", with: "").trimmingCharacters(in: .whitespaces) + var currentValue = Float(stringValue) ?? (self.slider?.floatValue ?? 0) * 100 + let step: Float = NSEvent.modifierFlags.contains(.shift) ? 10 : 1 + + currentValue += commandSelector == #selector(NSResponder.moveUp(_:)) ? step : -step + + let normalizedValue = max(0, min(100, currentValue)) + textView.string = "\(Int(normalizedValue))%" + + if let slider = self.slider { + slider.floatValue = normalizedValue / 100 + self.valueChanged(slider: slider) + } + + return true + } }