OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux / WpfDisplay / Views / ValueSlider.xaml.cs

402 lines · csharp ref:
#nullable enable
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;

using CommunityToolkit.Mvvm.Input;

using WpfDisplay.ViewModels;

namespace WpfDisplay.Views;

/// <summary>
/// Interaction logic for ValueSlider.xaml
/// </summary>
public partial class ValueSlider : UserControl
{
    [LibraryImport("User32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static partial bool SetCursorPos(int X, int Y);

    private static double IncrementMultiplier => 1.0
        * (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl) ? 10 : 1)
        * (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift) ? 0.1 : 1);


    /// <summary>
    /// Hide decimal places for integers, show fix 4 decimal places for double
    /// </summary>
    private void UpdateLabelFormat() => displayLabel.ContentStringFormat = (Increment % 1 == 0) ? "N0" : "N4";

    private void UpdateAnimatedSymbol()
    {
        Dispatcher.Invoke(() =>
        {

            char symbol = ' ';
            Brush symbolColor = Brushes.Gray;
            if (!string.IsNullOrEmpty(AnimationPath))
            {
                var main = (MainViewModel)System.Windows.Application.Current.MainWindow.DataContext;//ugh
                var animatedState = main.AnimationViewModel.GetChannelCurrentState(AnimationPath);
                symbol = animatedState switch
                {
                    null => '◇',
                    false => '◆',
                    true => '◈',
                };

                if (animatedState is not null)
                {
                    var isValueModified = main.AnimationViewModel.IsChannelBeingModified(AnimationPath, Value);
                    symbolColor = isValueModified ? Brushes.Orange : Brushes.White;
                }
            }
            symbolButton1.Content = symbol;
            symbolButton1.Foreground = symbolColor;
            symbolButton2.Content = symbol;
            symbolButton2.Foreground = symbolColor;
        });
    }


    public ValueSliderSettings? SliderSettings
    {
        get => (ValueSliderSettings?)GetValue(SliderSettingsProperty);
        set => SetValue(SliderSettingsProperty, value);
    }
    public static readonly DependencyProperty SliderSettingsProperty =
        DependencyProperty.Register("SliderSettings", typeof(ValueSliderSettings), typeof(ValueSlider), new FrameworkPropertyMetadata((s, e) =>
        {
            ValueSlider slider = (ValueSlider)s;
            ValueSliderSettings sliderSettings = (ValueSliderSettings)e.NewValue;
            if (sliderSettings is not null)
            {
                if (!BindingOperations.IsDataBound(s, LabelProperty))
                    slider.Label = sliderSettings.Label ?? slider.Label;
                if (!BindingOperations.IsDataBound(s, IsLabelShownProperty))
                    slider.IsLabelShown = sliderSettings.IsLabelShown ?? slider.IsLabelShown;
                if (!BindingOperations.IsDataBound(s, DefaultValueProperty))
                    slider.DefaultValue = sliderSettings.DefaultValue ?? slider.DefaultValue;
                if (!BindingOperations.IsDataBound(s, MinValueProperty))
                    slider.MinValue = sliderSettings.MinValue ?? slider.MinValue;
                if (!BindingOperations.IsDataBound(s, MaxValueProperty))
                    slider.MaxValue = sliderSettings.MaxValue ?? slider.MaxValue;
                if (!BindingOperations.IsDataBound(s, IncrementProperty))
                    slider.Increment = sliderSettings.Increment ?? slider.Increment;
                if (!BindingOperations.IsDataBound(s, AnimationPathProperty))
                    slider.AnimationPath = sliderSettings.AnimationPath ?? slider.AnimationPath;
                if (!BindingOperations.IsDataBound(s, ValueWillChangeActionProperty))
                    slider.ValueWillChangeAction = sliderSettings.ValueWillChange ?? slider.ValueWillChangeAction;
                if (!BindingOperations.IsDataBound(s, ValueChangedActionProperty))
                    slider.ValueChangedAction = sliderSettings.ValueChanged ?? slider.ValueChangedAction;
                if (!BindingOperations.IsDataBound(s, ValueDraggingStartedActionProperty))
                    slider.ValueDraggingStartedAction = sliderSettings.ValueDraggingStarted ?? slider.ValueDraggingStartedAction;
                if (!BindingOperations.IsDataBound(s, ValueDraggingCompletedActionProperty))
                    slider.ValueDraggingCompletedAction = sliderSettings.ValueDraggingCompleted ?? slider.ValueDraggingCompletedAction;
                if (!BindingOperations.IsDataBound(s, AnimateCommandProperty))
                    slider.AnimateCommand = sliderSettings.AnimateCommand ?? slider.AnimateCommand;
                //
                if (!BindingOperations.IsDataBound(s, ToolTipProperty))
                    slider.ToolTip = sliderSettings.ToolTip ?? slider.ToolTip;
            }
        }));

    public string Label
    {
        get => (string)GetValue(LabelProperty);
        set => SetValue(LabelProperty, value);
    }
    public static readonly DependencyProperty LabelProperty =
        DependencyProperty.Register("Label", typeof(string), typeof(ValueSlider), new PropertyMetadata(null));

    public bool IsLabelShown
    {
        get => (bool)GetValue(IsLabelShownProperty);
        set => SetValue(IsLabelShownProperty, value);
    }
    public static readonly DependencyProperty IsLabelShownProperty =
        DependencyProperty.Register("IsLabelShown", typeof(bool), typeof(ValueSlider), new PropertyMetadata(true));

    public double Value
    {
        get => (double)GetValue(ValueProperty);
        set => SetValue(ValueProperty, value);
    }
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(double), typeof(ValueSlider), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (s, e) =>
        {
            ((ValueSlider)s).UpdateAnimatedSymbol();
        }));

    public double DefaultValue
    {
        get => (double)GetValue(DefaultValueProperty);
        set => SetValue(DefaultValueProperty, value);
    }
    public static readonly DependencyProperty DefaultValueProperty =
        DependencyProperty.Register("DefaultValue", typeof(double), typeof(ValueSlider), new PropertyMetadata(0.0));

    public double Increment
    {
        get => (double)GetValue(IncrementProperty);
        set => SetValue(IncrementProperty, value);
    }
    public static readonly DependencyProperty IncrementProperty =
        DependencyProperty.Register("Increment", typeof(double), typeof(ValueSlider), new FrameworkPropertyMetadata(0.1, (s, e) =>
        {
            ((ValueSlider)s).UpdateLabelFormat();
        }));

    public double? MinValue
    {
        get => (double?)GetValue(MinValueProperty);
        set => SetValue(MinValueProperty, value);
    }
    public static readonly DependencyProperty MinValueProperty =
        DependencyProperty.Register("MinValue", typeof(double?), typeof(ValueSlider), new PropertyMetadata(null));

    public double? MaxValue
    {
        get => (double?)GetValue(MaxValueProperty);
        set => SetValue(MaxValueProperty, value);
    }
    public static readonly DependencyProperty MaxValueProperty =
        DependencyProperty.Register("MaxValue", typeof(double?), typeof(ValueSlider), new PropertyMetadata(null));

    public bool Editing
    {
        get => (bool)GetValue(EditingProperty);
        set => SetValue(EditingProperty, value);
    }
    public static readonly DependencyProperty EditingProperty =
        DependencyProperty.Register("Editing", typeof(bool), typeof(ValueSlider), new PropertyMetadata(false));

    public string AnimationPath
    {
        get => (string)GetValue(AnimationPathProperty);
        set => SetValue(AnimationPathProperty, value);
    }
    public static readonly DependencyProperty AnimationPathProperty =
        DependencyProperty.Register("AnimationPath", typeof(string), typeof(ValueSlider), new FrameworkPropertyMetadata(null, (s, e) =>
        {
            ((ValueSlider)s).UpdateAnimatedSymbol();
            ((ValueSlider)s).InternalAnimateCommand.NotifyCanExecuteChanged();
        }));

    public Action ValueWillChangeAction
    {
        get => (Action)GetValue(ValueWillChangeActionProperty);
        set => SetValue(ValueWillChangeActionProperty, value);
    }
    public static readonly DependencyProperty ValueWillChangeActionProperty =
        DependencyProperty.Register("ValueWillChangeAction", typeof(Action), typeof(ValueSlider), new PropertyMetadata(null));

    public Action<double> ValueChangedAction
    {
        get => (Action<double>)GetValue(ValueChangedActionProperty);
        set => SetValue(ValueChangedActionProperty, value);
    }
    public static readonly DependencyProperty ValueChangedActionProperty =
        DependencyProperty.Register("ValueChangedAction", typeof(Action<double>), typeof(ValueSlider), new PropertyMetadata(null));

    /// <summary>
    /// Invoked when the user starts changing the value with mouse dragging.
    /// </summary>
    public Action ValueDraggingStartedAction
    {
        get => (Action)GetValue(ValueDraggingStartedActionProperty);
        set => SetValue(ValueDraggingStartedActionProperty, value);
    }
    public static readonly DependencyProperty ValueDraggingStartedActionProperty =
        DependencyProperty.Register("ValueDraggingStartedAction", typeof(Action), typeof(ValueSlider), new PropertyMetadata(null));

    /// <summary>
    /// Invoked after the user has completed changing the value with mouse dragging.
    /// </summary>
    public Action<double> ValueDraggingCompletedAction
    {
        get => (Action<double>)GetValue(ValueDraggingCompletedActionProperty);
        set => SetValue(ValueDraggingCompletedActionProperty, value);
    }
    public static readonly DependencyProperty ValueDraggingCompletedActionProperty =
        DependencyProperty.Register("ValueDraggingCompletedAction", typeof(Action<double>), typeof(ValueSlider), new PropertyMetadata(null));

    private ICommand? _resetValueCommand;
    public ICommand ResetValueCommand => _resetValueCommand ??= new RelayCommand(() =>
    {
        ValueWillChangeAction?.Invoke();
        //Same as in Apophysis: reset to default value, or zero if value is already the default.
        if (Value == DefaultValue)
            Value = 0.0;
        else
            Value = DefaultValue;
        ValueChangedAction?.Invoke(Value);
    });

    public ICommand AnimateCommand
    {
        get => (ICommand)GetValue(AnimateCommandProperty);
        set => SetValue(AnimateCommandProperty, value);
    }
    public static readonly DependencyProperty AnimateCommandProperty =
        DependencyProperty.Register("AnimateCommand", typeof(ICommand), typeof(ValueSlider), new PropertyMetadata(null));

    //little hack for now
    private RelayCommand? _internalAnimateCommand;
    public RelayCommand InternalAnimateCommand => _internalAnimateCommand ??= new RelayCommand(() =>
    {
        AnimateCommand.Execute(new AnimationViewModel.AnimateValueCommandParameters(Label, AnimationPath, Value));
        UpdateAnimatedSymbol();
    }, () => AnimationPath is not null);

    public ValueSlider()
    {
        InitializeComponent();

        //little hack for now
        DataContextChanged += (s, e) =>
        {
            var vm = (MainViewModel)Application.Current.MainWindow.DataContext;
            vm.workspace.PropertyChanged += (s, e) => UpdateAnimatedSymbol();
            AnimateCommand = vm.AnimationViewModel.AnimateValueCommand;
            UpdateLabelFormat();
        };
    }

    private double ConstrainValue(double value)
    {
        if (MinValue != null)
            value = Math.Max((double)MinValue, value);
        if (MaxValue != null)
            value = Math.Min((double)MaxValue, value);
        return value;
    }

    private void Up_Click(object sender, RoutedEventArgs e)
    {
        ValueWillChangeAction?.Invoke();
        Value = ConstrainValue(Value + Increment * IncrementMultiplier);
        ValueChangedAction?.Invoke(Value);
    }

    private void Down_Click(object sender, RoutedEventArgs e)
    {
        ValueWillChangeAction?.Invoke();
        Value = ConstrainValue(Value - Increment * IncrementMultiplier);
        ValueChangedAction?.Invoke(Value);
    }

    private void ValueEditor_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Enter)
        {
            e.Handled = true;
            ValueWillChangeAction?.Invoke();
            StartEditing();
            ValueChangedAction?.Invoke(Value);
        }
        else if (e.Key == Key.Up)
        {
            e.Handled = true;
            Up_Click(sender, e);
        }
        else if (e.Key == Key.Down)
        {
            e.Handled = true;
            Down_Click(sender, e);
        }
        else if (e.Key == Key.Escape)
        {
            e.Handled = true;
            Value = _lastv;//restore
            valueEditor.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
            Editing = false;
            ValueChangedAction?.Invoke(Value);
        }
    }

    private Point _dragp;
    private double _lastv;//value before editing, used to restore previous value when pressing Esc

    private void DisplayLabel_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            if (!displayLabel.IsMouseCaptured)
            {
                ValueWillChangeAction?.Invoke();
                ValueDraggingStartedAction?.Invoke();
                _dragp = e.GetPosition(Window.GetWindow(this));
                _lastv = Value;
                displayLabel.CaptureMouse();
                Mouse.OverrideCursor = Cursors.None;
            }

            double delta = e.GetPosition(Window.GetWindow(this)).X - _dragp.X;
            Value = ConstrainValue(_lastv + delta * Increment * IncrementMultiplier);
            ValueChangedAction?.Invoke(Value);

            //reset position on mouseleave
            if (VisualTreeHelper.HitTest(this, e.GetPosition(displayLabel)) == null)
            {
                _lastv = Value;
                var pos = Window.GetWindow(this).PointToScreen(_dragp);
                SetCursorPos((int)Math.Round(pos.X), (int)Math.Round(pos.Y));
            }
        }

    }

    private void DisplayLabel_MouseUp(object sender, MouseButtonEventArgs e)
    {
        if (e.ChangedButton == MouseButton.Left)
        {
            if (!displayLabel.IsMouseCaptured)
            {//click
                StartEditing();
            }
            else
            {
                var pos = Window.GetWindow(this).PointToScreen(_dragp);
                SetCursorPos((int)Math.Round(pos.X), (int)Math.Round(pos.Y));
                ValueDraggingCompletedAction?.Invoke(Value);
            }
        }
        Mouse.OverrideCursor = null;//no override
        displayLabel.ReleaseMouseCapture();
    }

    private void StartEditing()
    {
        valueEditor.GetBindingExpression(TextBox.TextProperty).UpdateSource();
        _lastv = Value;
        Editing = true;
        valueEditor.Focus();
        valueEditor.SelectAll();
    }

    private void valueSlider_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        if (e.OldFocus != e.NewFocus)
        {
            StartEditing();
        }
    }

    private void valueEditor_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        ResetValueCommand.Execute(null);
        StartEditing();
    }

    private void ValueEditor_LostFocus(object sender, RoutedEventArgs e) => Editing = false;

    private void valueEditor_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) => Editing = false;

}