Browse Source

Number input improvements and brightness float values

Krzysztof Krysiński 1 year ago
parent
commit
25444bfaa9

+ 8 - 2
src/PixiEditor.AvaloniaUI/Helpers/Behaviours/TextBoxFocusBehavior.cs

@@ -47,6 +47,8 @@ internal class TextBoxFocusBehavior : Behavior<TextBox>
         set { SetValue(FocusNextProperty, value); }
     }
 
+    public static IInputElement FallbackFocusElement { get; set; }
+
     protected override void OnAttached()
     {
         base.OnAttached();
@@ -78,8 +80,11 @@ internal class TextBoxFocusBehavior : Behavior<TextBox>
 
     private void RemoveFocus()
     {
-        var next = KeyboardNavigationHandler.GetNext(AssociatedObject, NavigationDirection.Next);
+        var next = FocusNext
+            ? KeyboardNavigationHandler.GetNext(AssociatedObject, NavigationDirection.Next)
+            : FallbackFocusElement;
         NavigationMethod nextMethod = FocusNext ? NavigationMethod.Directional : NavigationMethod.Unspecified;
+        if (next == AssociatedObject) return;
         next?.Focus(nextMethod);
     }
 
@@ -87,7 +92,7 @@ internal class TextBoxFocusBehavior : Behavior<TextBox>
         object sender,
         GotFocusEventArgs e)
     {
-        if (SelectOnMouseClick || e.NavigationMethod == NavigationMethod.Tab)
+        if ((e.NavigationMethod == NavigationMethod.Pointer && SelectOnMouseClick) || e.NavigationMethod == NavigationMethod.Tab)
         {
             Dispatcher.UIThread.Post(() => AssociatedObject?.SelectAll(), DispatcherPriority.Input);
         }
@@ -106,6 +111,7 @@ internal class TextBoxFocusBehavior : Behavior<TextBox>
     {
         if (DeselectOnFocusLoss)
             AssociatedObject.ClearSelection();
+
         RemoveFocus();
     }
 

+ 1 - 1
src/PixiEditor.AvaloniaUI/Models/Handlers/Tools/IBrightnessToolHandler.cs

@@ -8,5 +8,5 @@ internal interface IBrightnessToolHandler : IToolHandler
     public BrightnessMode BrightnessMode { get; }
     public bool Darken { get; }
     public MouseButton UsedWith { get; }
-    public int CorrectionFactor { get; }
+    public float CorrectionFactor { get; }
 }

+ 2 - 1
src/PixiEditor.AvaloniaUI/ViewModels/Tools/ToolSettings/Settings/SizeSetting.cs

@@ -21,7 +21,8 @@ internal sealed class SizeSetting : Setting<int>
         {
             VerticalAlignment = VerticalAlignment.Center,
             MaxSize = 9999,
-            IsEnabled = true
+            IsEnabled = true,
+            FocusNext = false
         };
 
         Binding binding = new Binding("Value")

+ 1 - 1
src/PixiEditor.AvaloniaUI/ViewModels/Tools/Tools/BrightnessToolViewModel.cs

@@ -43,7 +43,7 @@ internal class BrightnessToolViewModel : ToolViewModel, IBrightnessToolHandler
     
     public bool Darken { get; private set; } = false;
 
-    int IBrightnessToolHandler.CorrectionFactor => (int)CorrectionFactor;
+    float IBrightnessToolHandler.CorrectionFactor => CorrectionFactor;
 
     public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {

+ 0 - 22
src/PixiEditor.AvaloniaUI/Views/Input/NumberInput.axaml

@@ -1,22 +0,0 @@
-<UserControl
-    x:Class="PixiEditor.AvaloniaUI.Views.Input.NumberInput"
-    x:ClassModifier="internal"
-    xmlns="https://github.com/avaloniaui"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-    xmlns:behaviours="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Behaviours"
-    xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters"
-    mc:Ignorable="d" d:DesignHeight="20" d:DesignWidth="40" x:Name="numberInput" Focusable="False">
-    <TextBox TextAlignment="Center" Focusable="True" Name="TextBox"
-             PointerWheelChanged="TextBox_MouseWheel"
-             LostFocus="TextBox_OnLostFocus"
-             Text="{Binding ElementName=numberInput, Path=FormattedValue, Mode=TwoWay}"
-             VerticalContentAlignment="Center">
-        <Interaction.Behaviors>
-            <behaviours:GlobalShortcutFocusBehavior/>
-            <behaviours:TextBoxFocusBehavior SelectOnMouseClick="True" ConfirmOnEnter="True"
-                                             FocusNext="{Binding ElementName=numberInput, Path=FocusNext}"/>
-        </Interaction.Behaviors>
-    </TextBox>
-</UserControl>

+ 91 - 14
src/PixiEditor.AvaloniaUI/Views/Input/NumberInput.axaml.cs → src/PixiEditor.AvaloniaUI/Views/Input/NumberInput.cs

@@ -4,12 +4,17 @@ using System.Linq;
 using System.Text.RegularExpressions;
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Data;
 using Avalonia.Input;
 using Avalonia.Interactivity;
+using Avalonia.Layout;
+using Avalonia.Media;
+using Avalonia.Xaml.Interactivity;
+using PixiEditor.AvaloniaUI.Helpers.Behaviours;
 
 namespace PixiEditor.AvaloniaUI.Views.Input;
 
-internal partial class NumberInput : UserControl
+internal partial class NumberInput : TextBox
 {
     public static readonly StyledProperty<double> ValueProperty =
         AvaloniaProperty.Register<NumberInput, double>(
@@ -32,6 +37,24 @@ internal partial class NumberInput : UserControl
         set => SetValue(FormattedValueProperty, value);
     }
 
+    public static readonly StyledProperty<bool> SelectOnMouseClickProperty = AvaloniaProperty.Register<NumberInput, bool>(
+        nameof(SelectOnMouseClick), true);
+
+    public static readonly StyledProperty<bool> ConfirmOnEnterProperty = AvaloniaProperty.Register<NumberInput, bool>(
+        nameof(ConfirmOnEnter), true);
+
+    public bool ConfirmOnEnter
+    {
+        get => GetValue(ConfirmOnEnterProperty);
+        set => SetValue(ConfirmOnEnterProperty, value);
+    }
+
+    public bool SelectOnMouseClick
+    {
+        get => GetValue(SelectOnMouseClickProperty);
+        set => SetValue(SelectOnMouseClickProperty, value);
+    }
+
     private static Regex regex;
 
     public int Decimals
@@ -80,6 +103,15 @@ internal partial class NumberInput : UserControl
         set { SetValue(FocusNextProperty, value); }
     }
 
+    private static readonly DataTable DataTable = new DataTable();
+    private static char[] allowedChars = new char[]
+    {
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '-', '*', '/', '(', ')', '.', ',', ' ',
+        'i', 'n', 'f', 't', 'y', 'e', 'I', 'N', 'F', 'T', 'Y', 'E'
+    };
+
+    protected override Type StyleKeyOverride => typeof(TextBox);
+
     static NumberInput()
     {
         ValueProperty.Changed.Subscribe(OnValueChanged);
@@ -88,7 +120,51 @@ internal partial class NumberInput : UserControl
 
     public NumberInput()
     {
-        InitializeComponent();
+        BehaviorCollection behaviors = Interaction.GetBehaviors(this);
+        behaviors.Add(new GlobalShortcutFocusBehavior());
+        TextBoxFocusBehavior behavior = new() { DeselectOnFocusLoss = true };
+        BindTextBoxBehavior(behavior);
+        behaviors.Add(behavior);
+        Interaction.SetBehaviors(this, behaviors);
+
+        Binding binding = new Binding(nameof(FormattedValue))
+        {
+            Source = this,
+            Mode = BindingMode.TwoWay
+        };
+
+        this.Bind(TextProperty, binding);
+
+        Focusable = true;
+        TextAlignment = TextAlignment.Center;
+        VerticalAlignment = VerticalAlignment.Center;
+    }
+
+    private void BindTextBoxBehavior(TextBoxFocusBehavior behavior)
+    {
+        Binding focusNextBinding = new Binding(nameof(FocusNext))
+        {
+            Source = this,
+            Mode = BindingMode.OneWay
+        };
+
+        behavior.Bind(TextBoxFocusBehavior.FocusNextProperty, focusNextBinding);
+
+        Binding selectOnMouseClickBinding = new Binding(nameof(SelectOnMouseClick))
+        {
+            Source = this,
+            Mode = BindingMode.OneWay
+        };
+
+        behavior.Bind(TextBoxFocusBehavior.SelectOnMouseClickProperty, selectOnMouseClickBinding);
+
+        Binding confirmOnEnterBinding = new Binding(nameof(ConfirmOnEnter))
+        {
+            Source = this,
+            Mode = BindingMode.OneWay
+        };
+
+        behavior.Bind(TextBoxFocusBehavior.ConfirmOnEnterProperty, confirmOnEnterBinding);
     }
 
     private static void OnValueChanged(AvaloniaPropertyChangedEventArgs<double> e)
@@ -131,20 +207,15 @@ internal partial class NumberInput : UserControl
         {
             return true;
         }
-        else
-        {
-            return TryEvaluateExpression(s, out value);
-        }
 
-        return false;
+        return TryEvaluateExpression(s, out value);
     }
 
     private static bool TryEvaluateExpression(string s, out double value)
     {
         try
         {
-            DataTable dt = new DataTable();
-            var computed = dt.Compute(s, "");
+            var computed = DataTable.Compute(s, "");
             if (IsNumber(computed))
             {
                 value = Convert.ChangeType(computed, typeof(double)) as double? ?? 0;
@@ -169,18 +240,23 @@ internal partial class NumberInput : UserControl
     private static void FormattedValueChanged(AvaloniaPropertyChangedEventArgs<string> e)
     {
         NumberInput input = (NumberInput)e.Sender;
-        /*if(ContainsInvalidCharacter(e.NewValue.Value))
+        if(ContainsInvalidCharacter(e.NewValue.Value))
         {
             input.FormattedValue = e.OldValue.Value;
-        }*/
+        }
     }
 
     private static bool ContainsInvalidCharacter(string text)
     {
-        return text.Any(c => !char.IsDigit(c) && c != '.' && c != ',');
+        if(text == null)
+        {
+            return false;
+        }
+
+        return text.Any(c => !allowedChars.Contains(c));
     }
 
-    private void TextBox_MouseWheel(object sender, PointerWheelEventArgs e)
+    protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
     {
         int step = (int)e.Delta.Y;
 
@@ -204,8 +280,9 @@ internal partial class NumberInput : UserControl
         OnScrollAction?.Invoke();
     }
 
-    private void TextBox_OnLostFocus(object? sender, RoutedEventArgs e)
+    protected override void OnLostFocus(RoutedEventArgs e)
     {
+        base.OnLostFocus(e);
         if (TryParse(FormattedValue, out double value))
         {
             Value = (float)Math.Round(Math.Clamp(value, Min, Max), Decimals);

+ 11 - 16
src/PixiEditor.AvaloniaUI/Views/Input/SizeInput.axaml

@@ -7,6 +7,7 @@
              xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
              xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters"
              xmlns:behaviours="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Behaviours"
+             xmlns:input="clr-namespace:PixiEditor.AvaloniaUI.Views.Input"
              mc:Ignorable="d" Focusable="True"
              d:DesignHeight="30" Name="uc"
              FlowDirection="LeftToRight">
@@ -29,24 +30,18 @@
                 <ColumnDefinition Width="2"/>
                 <ColumnDefinition Width="Auto"/>
             </Grid.ColumnDefinitions>
-            <TextBox IsEnabled="{Binding IsEnabled, ElementName=uc}" HorizontalContentAlignment="Right"
+            <input:NumberInput IsEnabled="{Binding IsEnabled, ElementName=uc}" HorizontalContentAlignment="Right"
                      BorderThickness="0" Background="Transparent"
-                     SelectionBrush="{DynamicResource ThemeControlHighlightColor}"
-                     Foreground="{Binding Foreground, ElementName=uc}" Focusable="True" CaretBrush="{Binding Foreground, ElementName=uc}"
+                     Foreground="{Binding Foreground, ElementName=uc}" Focusable="True"
                      Margin="0,0,5,0" VerticalAlignment="Center"
-                     x:Name="textBox"
-                     Text="{Binding Size, ElementName=uc, Converter={converters:ToolSizeToIntConverter}}"
-                     d:Text="22"
-                     MaxLength="6"
-                     MinWidth="43">
-                <Interaction.Behaviors>
-                    <behaviours:GlobalShortcutFocusBehavior/>
-                    <behaviours:TextBoxFocusBehavior 
-                        SelectOnMouseClick="{Binding BehaveLikeSmallEmbeddedField, ElementName=uc}" 
-                        ConfirmOnEnter="{Binding BehaveLikeSmallEmbeddedField, ElementName=uc}"
-                        DeselectOnFocusLoss="True"/>
-                </Interaction.Behaviors>
-            </TextBox>
+                     Decimals="0"
+                     x:Name="input"
+                     Value="{Binding Size, ElementName=uc, Mode=TwoWay}"
+                     d:Value="22"
+                     FocusNext="{Binding FocusNext, ElementName=uc}"
+                     SelectOnMouseClick="{Binding BehaveLikeSmallEmbeddedField, ElementName=uc}"
+                     ConfirmOnEnter="{Binding BehaveLikeSmallEmbeddedField, ElementName=uc}"
+                     MinWidth="43"/>
             <Grid Grid.Column="1" Background="{Binding BorderBrush, ElementName=border}"
                   d:Background="{DynamicResource ThemeAccentBrush}"/>
             <TextBlock ui:Translator.Key="{Binding Unit, ElementName=uc, Converter={converters:EnumToStringConverter}}" TextAlignment="Right"

+ 13 - 5
src/PixiEditor.AvaloniaUI/Views/Input/SizeInput.axaml.cs

@@ -22,6 +22,14 @@ internal partial class SizeInput : UserControl
     public static readonly StyledProperty<SizeUnit> UnitProperty =
         AvaloniaProperty.Register<SizeInput, SizeUnit>(nameof(Unit), defaultValue: SizeUnit.Pixel);
 
+    public static readonly StyledProperty<bool> FocusNextProperty = AvaloniaProperty.Register<SizeInput, bool>(
+        nameof(FocusNext), defaultValue: true);
+
+    public bool FocusNext
+    {
+        get => GetValue(FocusNextProperty);
+        set => SetValue(FocusNextProperty, value);
+    }
     public Action OnScrollAction
     {
         get { return GetValue(OnScrollActionProperty); }
@@ -66,8 +74,8 @@ internal partial class SizeInput : UserControl
 
     public void FocusAndSelect()
     {
-        textBox.Focus();
-        textBox.SelectAll();
+        input.Focus();
+        input.SelectAll();
     }
 
     private void Border_MouseLeftButtonDown(object? sender, PointerPressedEventArgs e)
@@ -81,10 +89,10 @@ internal partial class SizeInput : UserControl
         else
             textBox.CaretIndex = charIndex;*/
         //TODO: Above functions not found in Avalonia
-        textBox.SelectAll();
+        input.SelectAll();
         e.Handled = true;
-        if (!textBox.IsFocused)
-            textBox.Focus();
+        if (!input.IsFocused)
+            input.Focus();
     }
 
     public SizeUnit Unit

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/MainView.axaml

@@ -21,7 +21,7 @@
     <Grid>
         <DockPanel>
             <main1:MainTitleBar DockPanel.Dock="Top" />
-            <Grid Focusable="True">
+            <Grid Focusable="True" Name="FocusableGrid">
                 <Grid.RowDefinitions>
                     <RowDefinition Height="40" />
                     <RowDefinition Height="*" />

+ 2 - 0
src/PixiEditor.AvaloniaUI/Views/MainView.axaml.cs

@@ -1,4 +1,5 @@
 using Avalonia.Controls;
+using PixiEditor.AvaloniaUI.Helpers.Behaviours;
 
 namespace PixiEditor.AvaloniaUI.Views;
 
@@ -7,5 +8,6 @@ public partial class MainView : UserControl
     public MainView()
     {
         InitializeComponent();
+        TextBoxFocusBehavior.FallbackFocusElement = FocusableGrid;
     }
 }