Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 109 additions & 27 deletions SkEditor/API/Settings/Types/SliderSetting.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
using System;
using System.Timers;
using Avalonia.Controls;
using Avalonia.Layout;
using FluentAvalonia.UI.Controls;
using Newtonsoft.Json.Linq;

namespace SkEditor.API.Settings.Types;

/// <summary>
/// Represent a slider setting.
/// </summary>
public class SliderSetting(double min = 0.0, double max = 100.0, double tickFrequency = 1.0, bool isSnapToTickEnabled = true, bool debounce = true) : ISettingType
public class SliderSetting(
double min = 0.0,
double max = 100.0,
double tickFrequency = 1.0,
bool showAlternativeInput = true,
bool isSnapToTickEnabled = true,
bool debounce = true
) : ISettingType
{
public double Min { get; } = min;
public double Max { get; } = max;
public double TickFrequency { get; } = tickFrequency;
public bool IsSnapToTickEnabled { get; } = isSnapToTickEnabled;
public bool Debounce { get; } = debounce;

public bool ShowAlternativeInput { get; } = showAlternativeInput;

public object Deserialize(JToken value)
{
return value.Value<double>();
Expand All @@ -28,43 +38,115 @@ public JToken Serialize(object value)

public Control CreateControl(object raw, Action<object> onChanged)
{
double value = (double)raw;
Slider slider = new()
double initial = (double)raw;

var slider = CreateSlider(initial);
NumberBox? numberBox = ShowAlternativeInput ? CreateNumberBox(initial) : null;

if (Debounce)
{
SetupDebouncedSlider(slider, numberBox, onChanged);
}
else
{
Minimum = Min,
Maximum = Max,
SetupImmediateSlider(slider, numberBox, onChanged);
}

if (ShowAlternativeInput && numberBox is not null)
{
SetupNumberBox(numberBox, slider, onChanged);

var panel = new StackPanel
{
Orientation = Orientation.Horizontal,
Spacing = 5
};

panel.Children.Add(numberBox);
panel.Children.Add(slider);
return panel;
}

return slider;
}

private Slider CreateSlider(double value)
{
return new Slider
{
Minimum = Min,
Maximum = Max,
Value = value,
TickFrequency = TickFrequency,
IsSnapToTickEnabled = IsSnapToTickEnabled,
Width = 150,
MinWidth = 50,
};

if (Debounce)
}

private NumberBox CreateNumberBox(double value)
{
return new NumberBox
{
Minimum = Min,
Maximum = Max,
Value = value,
VerticalAlignment = VerticalAlignment.Center,
};
}

private static void SetupImmediateSlider(Slider slider, NumberBox? numberBox, Action<object> onChanged)
{
slider.ValueChanged += (_, _) =>
{
onChanged(slider.Value);
if (numberBox is not null)
{
numberBox.Value = slider.Value;
}
};
}

private static void SetupDebouncedSlider(Slider slider, NumberBox? numberBox, Action<object> onChanged)
{
var debounceTimer = new Timer(300) { AutoReset = false };
double latestValue = slider.Value;

debounceTimer.Elapsed += (_, _) =>
{
Timer? debounceTimer = null;
slider.ValueChanged += (_, args) =>
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() =>
{
debounceTimer?.Stop();
debounceTimer = new Timer(300);
debounceTimer.Elapsed += (_, _) =>
onChanged(latestValue);
if (numberBox is not null)
{
debounceTimer.Stop();
Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(() => onChanged(slider.Value));
};
debounceTimer.AutoReset = false;
debounceTimer.Start();
};
}
else
numberBox.Value = slider.Value;
}
});
};

slider.ValueChanged += (_, _) =>
{
latestValue = slider.Value;
debounceTimer.Stop();
debounceTimer.Start();
};
}

private static void SetupNumberBox(NumberBox numberBox, Slider slider, Action<object> onChanged)
{
numberBox.ValueChanged += (_, _) =>
{
slider.ValueChanged += (_, _) =>
var newValue = numberBox.Value;

if (Double.IsNaN(newValue))
{
onChanged(slider.Value);
};
}

return slider;
numberBox.Value = slider.Value;
return;
}

onChanged(newValue);
slider.Value = newValue;
};
}

public bool IsSelfManaged => false;
Expand Down