From 0f31db374acf65643e9d2189e70ac0f1a6860f8a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 01:35:39 +0000 Subject: [PATCH 1/6] Implement slider translation curve presets and custom curve settings Co-authored-by: PalmarHealer <93807726+PalmarHealer@users.noreply.github.com> --- ControlPad/EventHandler.cs | 2 +- ControlPad/Settings.cs | 72 +++++++++++++++++- ControlPad/SliderTranslationCurve.cs | 76 +++++++++++++++++++ .../UI Elements/SettingsUserControl.xaml | 69 +++++++++++++---- .../UI Elements/SettingsUserControl.xaml.cs | 46 +++++++++-- 5 files changed, 244 insertions(+), 21 deletions(-) create mode 100644 ControlPad/SliderTranslationCurve.cs diff --git a/ControlPad/EventHandler.cs b/ControlPad/EventHandler.cs index 18fc471..4cc74b9 100644 --- a/ControlPad/EventHandler.cs +++ b/ControlPad/EventHandler.cs @@ -189,7 +189,7 @@ private float SliderToFloat(int value, int mode = 0) if (normalized < 0.005f) return 0f; - return (float)Math.Pow(normalized, Settings.TranslationExponent); + return SliderTranslationCurve.Apply(normalized); } } } diff --git a/ControlPad/Settings.cs b/ControlPad/Settings.cs index c1d97dd..46f3005 100644 --- a/ControlPad/Settings.cs +++ b/ControlPad/Settings.cs @@ -15,6 +15,11 @@ public static class Settings private static bool _startMinimized = false; private static bool _minimizeToSystemTray = true; private static double _translationExponent = 1d; + private static string _translationCurvePreset = "linear"; + private static double _translationCurveX1 = 0d; + private static double _translationCurveY1 = 0d; + private static double _translationCurveX2 = 1d; + private static double _translationCurveY2 = 1d; private static int _selectedThemeIndex = 0; private static int _selectedBackgroundIndex = 3; private static int _sliderDeadZone = 4; @@ -115,6 +120,56 @@ public static double TranslationExponent } } + public static string TranslationCurvePreset + { + get => _translationCurvePreset; + set + { + _translationCurvePreset = SliderTranslationCurve.IsSupportedPreset(value) ? value : "linear"; + Save(); + } + } + + public static double TranslationCurveX1 + { + get => _translationCurveX1; + set + { + _translationCurveX1 = Math.Clamp(value, 0d, 1d); + Save(); + } + } + + public static double TranslationCurveY1 + { + get => _translationCurveY1; + set + { + _translationCurveY1 = Math.Clamp(value, 0d, 1d); + Save(); + } + } + + public static double TranslationCurveX2 + { + get => _translationCurveX2; + set + { + _translationCurveX2 = Math.Clamp(value, 0d, 1d); + Save(); + } + } + + public static double TranslationCurveY2 + { + get => _translationCurveY2; + set + { + _translationCurveY2 = Math.Clamp(value, 0d, 1d); + Save(); + } + } + private class Data { public bool TrayIconMessageShown { get; set; } = false; @@ -122,6 +177,11 @@ private class Data public bool StartMinimized { get; set; } = true; public bool MinimizeToSystemTray { get; set; } = true; public double TranslationExponent { get; set; } = 1d; + public string TranslationCurvePreset { get; set; } = "linear"; + public double TranslationCurveX1 { get; set; } = 0d; + public double TranslationCurveY1 { get; set; } = 0d; + public double TranslationCurveX2 { get; set; } = 1d; + public double TranslationCurveY2 { get; set; } = 1d; public int SelectedThemeIndex { get; set; } = 0; public int SelectedBackgroundIndex { get; set; } = 3; public int SliderDeadZone { get; set; } = 4; @@ -148,6 +208,11 @@ public static void Load() _selectedBackgroundIndex = data.SelectedBackgroundIndex; _sliderDeadZone = data.SliderDeadZone; _translationExponent = data.TranslationExponent; + _translationCurvePreset = SliderTranslationCurve.IsSupportedPreset(data.TranslationCurvePreset) ? data.TranslationCurvePreset : "linear"; + _translationCurveX1 = Math.Clamp(data.TranslationCurveX1, 0d, 1d); + _translationCurveY1 = Math.Clamp(data.TranslationCurveY1, 0d, 1d); + _translationCurveX2 = Math.Clamp(data.TranslationCurveX2, 0d, 1d); + _translationCurveY2 = Math.Clamp(data.TranslationCurveY2, 0d, 1d); _unmuteOnSliderChange = data.UnmuteOnSliderChange; } catch @@ -170,6 +235,11 @@ private static void Save() SelectedBackgroundIndex = _selectedBackgroundIndex, SliderDeadZone = _sliderDeadZone, TranslationExponent = _translationExponent, + TranslationCurvePreset = _translationCurvePreset, + TranslationCurveX1 = _translationCurveX1, + TranslationCurveY1 = _translationCurveY1, + TranslationCurveX2 = _translationCurveX2, + TranslationCurveY2 = _translationCurveY2, UnmuteOnSliderChange = _unmuteOnSliderChange, }; @@ -183,4 +253,4 @@ private static void Save() } } } -} \ No newline at end of file +} diff --git a/ControlPad/SliderTranslationCurve.cs b/ControlPad/SliderTranslationCurve.cs new file mode 100644 index 0000000..c69a569 --- /dev/null +++ b/ControlPad/SliderTranslationCurve.cs @@ -0,0 +1,76 @@ +namespace ControlPad +{ + public static class SliderTranslationCurve + { + private const double Epsilon = 1e-6; + + public static bool IsSupportedPreset(string? preset) + { + return preset is "ease" or "linear" or "ease-in" or "ease-out" or "ease-in-out" or "custom"; + } + + public static (double x1, double y1, double x2, double y2) GetPresetControlPoints(string preset) + { + return preset switch + { + "ease" => (0.25d, 0.1d, 0.25d, 1d), + "linear" => (0d, 0d, 1d, 1d), + "ease-in" => (0.42d, 0d, 1d, 1d), + "ease-out" => (0d, 0d, 0.58d, 1d), + "ease-in-out" => (0.42d, 0d, 0.58d, 1d), + _ => (0d, 0d, 1d, 1d), + }; + } + + public static float Apply(float input) + { + double t = Math.Clamp(input, 0f, 1f); + var controlPoints = Settings.TranslationCurvePreset == "custom" + ? (Settings.TranslationCurveX1, Settings.TranslationCurveY1, Settings.TranslationCurveX2, Settings.TranslationCurveY2) + : GetPresetControlPoints(Settings.TranslationCurvePreset); + + return (float)Evaluate(controlPoints.Item1, controlPoints.Item2, controlPoints.Item3, controlPoints.Item4, t); + } + + private static double Evaluate(double x1, double y1, double x2, double y2, double xTarget) + { + x1 = Math.Clamp(x1, 0d, 1d); + x2 = Math.Clamp(x2, 0d, 1d); + y1 = Math.Clamp(y1, 0d, 1d); + y2 = Math.Clamp(y2, 0d, 1d); + xTarget = Math.Clamp(xTarget, 0d, 1d); + + if (xTarget <= 0d) + return 0d; + if (xTarget >= 1d) + return 1d; + + double lower = 0d; + double upper = 1d; + double t = xTarget; + + for (int i = 0; i < 20; i++) + { + t = (lower + upper) * 0.5d; + double x = Bezier(t, 0d, x1, x2, 1d); + if (Math.Abs(x - xTarget) < Epsilon) + break; + if (x < xTarget) + lower = t; + else + upper = t; + } + + return Bezier(t, 0d, y1, y2, 1d); + } + + private static double Bezier(double t, double p0, double p1, double p2, double p3) + { + double oneMinusT = 1d - t; + return oneMinusT * oneMinusT * oneMinusT * p0 + + 3d * oneMinusT * oneMinusT * t * p1 + + 3d * oneMinusT * t * t * p2 + + t * t * t * p3; + } + } +} diff --git a/ControlPad/UI Elements/SettingsUserControl.xaml b/ControlPad/UI Elements/SettingsUserControl.xaml index 234baf6..d40e4f3 100644 --- a/ControlPad/UI Elements/SettingsUserControl.xaml +++ b/ControlPad/UI Elements/SettingsUserControl.xaml @@ -128,7 +128,7 @@ Unchecked="cb_UnmuteOnSliderChange_Checked"/> - + @@ -136,23 +136,66 @@ - - + + + + + + + + + + + + + + + + + diff --git a/ControlPad/UI Elements/SettingsUserControl.xaml.cs b/ControlPad/UI Elements/SettingsUserControl.xaml.cs index dfae4c5..aabe14f 100644 --- a/ControlPad/UI Elements/SettingsUserControl.xaml.cs +++ b/ControlPad/UI Elements/SettingsUserControl.xaml.cs @@ -20,6 +20,7 @@ public partial class SettingsUserControl : UserControl { private readonly bool _isInitialized = false; private readonly MainWindow _mainWindow; + private static readonly string[] TranslationCurvePresets = { "ease", "linear", "ease-in", "ease-out", "ease-in-out", "custom" }; public SettingsUserControl(MainWindow mainWindow) { @@ -88,15 +89,36 @@ private void BackgroundComboBox_SelectionChanged(object sender, SelectionChanged Settings.SelectedBackgroundIndex = BackgroundComboBox.SelectedIndex; } - private void nb_TranslationExponent_ValueChanged(object sender, Wpf.Ui.Controls.NumberBoxValueChangedEventArgs e) + private void TranslationCurvePresetComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (!_isInitialized) return; - if (nb_TranslationExponent.Value != null) - Settings.TranslationExponent = (double)nb_TranslationExponent.Value; - else - Settings.TranslationExponent = 1.0d; + string preset = TranslationCurvePresets[Math.Clamp(TranslationCurvePresetComboBox.SelectedIndex, 0, TranslationCurvePresets.Length - 1)]; + Settings.TranslationCurvePreset = preset; + + if (preset != "custom") + { + var cp = SliderTranslationCurve.GetPresetControlPoints(preset); + Settings.TranslationCurveX1 = cp.x1; + Settings.TranslationCurveY1 = cp.y1; + Settings.TranslationCurveX2 = cp.x2; + Settings.TranslationCurveY2 = cp.y2; + SetCustomCurveControls(cp.x1, cp.y1, cp.x2, cp.y2); + } + + CustomCurveGrid.IsEnabled = preset == "custom"; + } + + private void nb_CustomCurve_ValueChanged(object sender, Wpf.Ui.Controls.NumberBoxValueChangedEventArgs e) + { + if (!_isInitialized || Settings.TranslationCurvePreset != "custom") + return; + + Settings.TranslationCurveX1 = nb_CurveX1.Value ?? 0d; + Settings.TranslationCurveY1 = nb_CurveY1.Value ?? 0d; + Settings.TranslationCurveX2 = nb_CurveX2.Value ?? 1d; + Settings.TranslationCurveY2 = nb_CurveY2.Value ?? 1d; } public static void ChangeAppTheme(int index) @@ -145,7 +167,19 @@ public void SetControls() cb_UnmuteOnSliderChange.IsChecked = Settings.UnmuteOnSliderChange; ThemeComboBox.SelectedIndex = Settings.SelectedThemeIndex; BackgroundComboBox.SelectedIndex = Settings.SelectedBackgroundIndex; - nb_TranslationExponent.Value = Settings.TranslationExponent; + + int presetIndex = Array.IndexOf(TranslationCurvePresets, Settings.TranslationCurvePreset); + TranslationCurvePresetComboBox.SelectedIndex = presetIndex >= 0 ? presetIndex : 1; + SetCustomCurveControls(Settings.TranslationCurveX1, Settings.TranslationCurveY1, Settings.TranslationCurveX2, Settings.TranslationCurveY2); + CustomCurveGrid.IsEnabled = Settings.TranslationCurvePreset == "custom"; + } + + private void SetCustomCurveControls(double x1, double y1, double x2, double y2) + { + nb_CurveX1.Value = x1; + nb_CurveY1.Value = y1; + nb_CurveX2.Value = x2; + nb_CurveY2.Value = y2; } private void Btn_Presets_Click(object sender, RoutedEventArgs e) From cf984cdd3079668bfe697984b154985982036768 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 01:36:35 +0000 Subject: [PATCH 2/6] Optimize curve mapping and suppress redundant UI events Co-authored-by: PalmarHealer <93807726+PalmarHealer@users.noreply.github.com> --- ControlPad/SliderTranslationCurve.cs | 36 ++++++++++++++++--- .../UI Elements/SettingsUserControl.xaml.cs | 19 +++++++--- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/ControlPad/SliderTranslationCurve.cs b/ControlPad/SliderTranslationCurve.cs index c69a569..700f2ec 100644 --- a/ControlPad/SliderTranslationCurve.cs +++ b/ControlPad/SliderTranslationCurve.cs @@ -3,6 +3,13 @@ namespace ControlPad public static class SliderTranslationCurve { private const double Epsilon = 1e-6; + private const int MaxBisectionIterations = 20; + private static string? _lastPreset; + private static double _lastX1; + private static double _lastY1; + private static double _lastX2; + private static double _lastY2; + private static (double x1, double y1, double x2, double y2) _cachedControlPoints = (0d, 0d, 1d, 1d); public static bool IsSupportedPreset(string? preset) { @@ -25,13 +32,34 @@ public static (double x1, double y1, double x2, double y2) GetPresetControlPoint public static float Apply(float input) { double t = Math.Clamp(input, 0f, 1f); - var controlPoints = Settings.TranslationCurvePreset == "custom" - ? (Settings.TranslationCurveX1, Settings.TranslationCurveY1, Settings.TranslationCurveX2, Settings.TranslationCurveY2) - : GetPresetControlPoints(Settings.TranslationCurvePreset); + var controlPoints = GetControlPoints(); return (float)Evaluate(controlPoints.Item1, controlPoints.Item2, controlPoints.Item3, controlPoints.Item4, t); } + private static (double x1, double y1, double x2, double y2) GetControlPoints() + { + string preset = Settings.TranslationCurvePreset; + double x1 = Settings.TranslationCurveX1; + double y1 = Settings.TranslationCurveY1; + double x2 = Settings.TranslationCurveX2; + double y2 = Settings.TranslationCurveY2; + + if (_lastPreset == preset && _lastX1 == x1 && _lastY1 == y1 && _lastX2 == x2 && _lastY2 == y2) + return _cachedControlPoints; + + _cachedControlPoints = preset == "custom" + ? (x1, y1, x2, y2) + : GetPresetControlPoints(preset); + + _lastPreset = preset; + _lastX1 = x1; + _lastY1 = y1; + _lastX2 = x2; + _lastY2 = y2; + return _cachedControlPoints; + } + private static double Evaluate(double x1, double y1, double x2, double y2, double xTarget) { x1 = Math.Clamp(x1, 0d, 1d); @@ -49,7 +77,7 @@ private static double Evaluate(double x1, double y1, double x2, double y2, doubl double upper = 1d; double t = xTarget; - for (int i = 0; i < 20; i++) + for (int i = 0; i < MaxBisectionIterations; i++) { t = (lower + upper) * 0.5d; double x = Bezier(t, 0d, x1, x2, 1d); diff --git a/ControlPad/UI Elements/SettingsUserControl.xaml.cs b/ControlPad/UI Elements/SettingsUserControl.xaml.cs index aabe14f..18632c9 100644 --- a/ControlPad/UI Elements/SettingsUserControl.xaml.cs +++ b/ControlPad/UI Elements/SettingsUserControl.xaml.cs @@ -21,6 +21,7 @@ public partial class SettingsUserControl : UserControl private readonly bool _isInitialized = false; private readonly MainWindow _mainWindow; private static readonly string[] TranslationCurvePresets = { "ease", "linear", "ease-in", "ease-out", "ease-in-out", "custom" }; + private bool _suppressCustomCurveEvents = false; public SettingsUserControl(MainWindow mainWindow) { @@ -112,7 +113,7 @@ private void TranslationCurvePresetComboBox_SelectionChanged(object sender, Sele private void nb_CustomCurve_ValueChanged(object sender, Wpf.Ui.Controls.NumberBoxValueChangedEventArgs e) { - if (!_isInitialized || Settings.TranslationCurvePreset != "custom") + if (!_isInitialized || _suppressCustomCurveEvents || Settings.TranslationCurvePreset != "custom") return; Settings.TranslationCurveX1 = nb_CurveX1.Value ?? 0d; @@ -176,10 +177,18 @@ public void SetControls() private void SetCustomCurveControls(double x1, double y1, double x2, double y2) { - nb_CurveX1.Value = x1; - nb_CurveY1.Value = y1; - nb_CurveX2.Value = x2; - nb_CurveY2.Value = y2; + _suppressCustomCurveEvents = true; + try + { + nb_CurveX1.Value = x1; + nb_CurveY1.Value = y1; + nb_CurveX2.Value = x2; + nb_CurveY2.Value = y2; + } + finally + { + _suppressCustomCurveEvents = false; + } } private void Btn_Presets_Click(object sender, RoutedEventArgs e) From 40394631e880ab3a1f9c7450b0862560269c9a8d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 02:48:33 +0000 Subject: [PATCH 3/6] Add scrolling overflow to settings panel Co-authored-by: PalmarHealer <93807726+PalmarHealer@users.noreply.github.com> --- ControlPad/UI Elements/SettingsUserControl.xaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ControlPad/UI Elements/SettingsUserControl.xaml b/ControlPad/UI Elements/SettingsUserControl.xaml index d40e4f3..809aad1 100644 --- a/ControlPad/UI Elements/SettingsUserControl.xaml +++ b/ControlPad/UI Elements/SettingsUserControl.xaml @@ -10,7 +10,8 @@ > - + @@ -275,6 +276,6 @@ - + From 954c38d43475817a81783988fd9125dd1049d831 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 02:49:23 +0000 Subject: [PATCH 4/6] Preserve settings layout while enabling scroll overflow Co-authored-by: PalmarHealer <93807726+PalmarHealer@users.noreply.github.com> --- ControlPad/UI Elements/SettingsUserControl.xaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ControlPad/UI Elements/SettingsUserControl.xaml b/ControlPad/UI Elements/SettingsUserControl.xaml index 809aad1..5c00be6 100644 --- a/ControlPad/UI Elements/SettingsUserControl.xaml +++ b/ControlPad/UI Elements/SettingsUserControl.xaml @@ -12,7 +12,8 @@ - + + @@ -275,7 +276,8 @@ - + + From 704661bfc96aa3b4630a3c93c514e6f41a25e465 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:00:43 +0000 Subject: [PATCH 5/6] Revert "Preserve settings layout while enabling scroll overflow" This reverts commit 954c38d43475817a81783988fd9125dd1049d831. --- ControlPad/UI Elements/SettingsUserControl.xaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ControlPad/UI Elements/SettingsUserControl.xaml b/ControlPad/UI Elements/SettingsUserControl.xaml index 5c00be6..809aad1 100644 --- a/ControlPad/UI Elements/SettingsUserControl.xaml +++ b/ControlPad/UI Elements/SettingsUserControl.xaml @@ -12,8 +12,7 @@ - - + @@ -276,8 +275,7 @@ - - + From 2571daa3e540ba026b27c9efb2aa171bf916b93d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:00:43 +0000 Subject: [PATCH 6/6] Revert "Add scrolling overflow to settings panel" This reverts commit 40394631e880ab3a1f9c7450b0862560269c9a8d. --- ControlPad/UI Elements/SettingsUserControl.xaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ControlPad/UI Elements/SettingsUserControl.xaml b/ControlPad/UI Elements/SettingsUserControl.xaml index 809aad1..d40e4f3 100644 --- a/ControlPad/UI Elements/SettingsUserControl.xaml +++ b/ControlPad/UI Elements/SettingsUserControl.xaml @@ -10,8 +10,7 @@ > - + @@ -276,6 +275,6 @@ - +