Krzysztof Krysiński 2 gadi atpakaļ
vecāks
revīzija
c7320f7ab7

+ 33 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/Behaviours/GlobalShortcutFocusBehavior.cs

@@ -0,0 +1,33 @@
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Xaml.Interactivity;
+using PixiEditor.Models.Controllers;
+
+namespace PixiEditor.Helpers.Behaviours;
+
+internal class GlobalShortcutFocusBehavior : Behavior<Control>
+{
+    protected override void OnAttached()
+    {
+        base.OnAttached();
+        AssociatedObject.AddHandler(InputElement.GotFocusEvent, AssociatedObject_GotKeyboardFocus);
+        AssociatedObject.AddHandler(InputElement.LostFocusEvent, AssociatedObject_LostKeyboardFocus);
+    }
+
+    protected override void OnDetaching()
+    {
+        base.OnDetaching();
+        AssociatedObject.RemoveHandler(InputElement.GotFocusEvent, AssociatedObject_GotKeyboardFocus);
+        AssociatedObject.RemoveHandler(InputElement.LostFocusEvent, AssociatedObject_LostKeyboardFocus);
+    }
+
+    private void AssociatedObject_LostKeyboardFocus(object sender)
+    {
+        ShortcutController.UnblockShortcutExecution("GlobalShortcutFocusBehavior");
+    }
+
+    private void AssociatedObject_GotKeyboardFocus(object sender)
+    {
+        ShortcutController.BlockShortcutExecution("GlobalShortcutFocusBehavior");
+    }
+}

+ 125 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/Behaviours/TextBoxFocusBehavior.cs

@@ -0,0 +1,125 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Xaml.Interactivity;
+
+namespace PixiEditor.Helpers.Behaviours;
+
+internal class TextBoxFocusBehavior : Behavior<TextBox>
+{
+    public static readonly StyledProperty<bool> SelectOnMouseClickProperty =
+        AvaloniaProperty.Register<TextBoxFocusBehavior, bool>(
+            nameof(SelectOnMouseClick));
+
+    public static readonly StyledProperty<bool> ConfirmOnEnterProperty =
+        AvaloniaProperty.Register<TextBoxFocusBehavior, bool>(
+            nameof(ConfirmOnEnter));
+
+    public static readonly StyledProperty<bool> DeselectOnFocusLossProperty =
+        AvaloniaProperty.Register<TextBoxFocusBehavior, bool>(
+            nameof(DeselectOnFocusLoss));
+
+    public bool SelectOnMouseClick
+    {
+        get => (bool)GetValue(SelectOnMouseClickProperty);
+        set => SetValue(SelectOnMouseClickProperty, value);
+    }
+
+    public bool ConfirmOnEnter
+    {
+        get => (bool)GetValue(ConfirmOnEnterProperty);
+        set => SetValue(ConfirmOnEnterProperty, value);
+    }
+    public bool DeselectOnFocusLoss
+    {
+        get => (bool)GetValue(DeselectOnFocusLossProperty);
+        set => SetValue(DeselectOnFocusLossProperty, value);
+    }
+
+    public static readonly StyledProperty<bool> FocusNextProperty =
+        AvaloniaProperty.Register<TextBoxFocusBehavior, bool>(nameof(FocusNext));
+
+    public bool FocusNext
+    {
+        get { return (bool)GetValue(FocusNextProperty); }
+        set { SetValue(FocusNextProperty, value); }
+    }
+
+    protected override void OnAttached()
+    {
+        base.OnAttached();
+        AssociatedObject.GotFocus += AssociatedObjectGotKeyboardFocus;
+        AssociatedObject.PointerPressed += AssociatedObjectGotMouseCapture;
+        AssociatedObject.LostFocus += AssociatedObject_LostFocus;
+        AssociatedObject.PointerPressed += OnPointerPressed;
+        AssociatedObject.KeyUp += AssociatedObject_KeyUp;
+    }
+
+    protected override void OnDetaching()
+    {
+        base.OnDetaching();
+        AssociatedObject.GotFocus -= AssociatedObjectGotKeyboardFocus;
+        AssociatedObject.PointerPressed -= AssociatedObjectGotMouseCapture;
+        AssociatedObject.LostFocus -= AssociatedObject_LostFocus;
+        AssociatedObject.PointerPressed -= OnPointerPressed;
+        AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
+    }
+
+    // Converts number to proper format if enter is clicked and moves focus to next object
+    private void AssociatedObject_KeyUp(object sender, KeyEventArgs e)
+    {
+        if (e.Key != Key.Enter || !ConfirmOnEnter)
+            return;
+
+        RemoveFocus();
+    }
+
+    private void RemoveFocus()
+    {
+        if (!FocusNext)
+        {
+            //TODO: FocusManager is private api
+            //FocusHelper.MoveFocusToParent(AssociatedObject);
+        }
+        else
+        {
+            //TODO: Idk if it works
+            AssociatedObject.Focus(NavigationMethod.Directional);
+        }
+    }
+
+    private void AssociatedObjectGotKeyboardFocus(
+        object sender,
+        GotFocusEventArgs e)
+    {
+        if (SelectOnMouseClick || e.NavigationMethod == NavigationMethod.Tab)
+            AssociatedObject.SelectAll();
+    }
+
+    private void AssociatedObjectGotMouseCapture(
+        object? sender, PointerPressedEventArgs pointerPressedEventArgs)
+    {
+        if (SelectOnMouseClick)
+            AssociatedObject.SelectAll();
+    }
+
+    private void AssociatedObject_LostFocus(object sender, RoutedEventArgs e)
+    {
+        if (DeselectOnFocusLoss)
+            AssociatedObject.ClearSelection();
+        RemoveFocus();
+    }
+
+    private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
+    {
+        if (!SelectOnMouseClick)
+            return;
+
+        if (!AssociatedObject.IsKeyboardFocusWithin)
+        {
+            AssociatedObject.Focus();
+            e.Handled = true;
+        }
+    }
+}

+ 57 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/InputBox.cs

@@ -0,0 +1,57 @@
+using System.Windows.Input;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Hardware.Info;
+using PixiEditor.Models.Events;
+
+namespace PixiEditor.Views.UserControls;
+
+internal class InputBox : TextBox
+{
+    public ICommand SubmitCommand
+    {
+        get { return (ICommand)GetValue(SubmitCommandProperty); }
+        set { SetValue(SubmitCommandProperty, value); }
+    }
+
+
+    public static readonly StyledProperty<ICommand> SubmitCommandProperty =
+        AvaloniaProperty.Register<InputBox, ICommand>(nameof(SubmitCommand));
+
+    public object SubmitCommandParameter
+    {
+        get { return (object)GetValue(SubmitCommandParameterProperty); }
+        set { SetValue(SubmitCommandParameterProperty, value); }
+    }
+
+
+    public static readonly StyledProperty<object> SubmitCommandParameterProperty =
+        AvaloniaProperty.Register<InputBox, object>(nameof(SubmitCommandParameter));
+
+    public event EventHandler<InputBoxEventArgs> OnSubmit;
+
+    protected override void OnLostFocus(RoutedEventArgs e)
+    {
+        OnSubmit?.Invoke(this, new InputBoxEventArgs(Text));
+        //TODO: Keyboard.ClearFocus();
+        //Keyboard.ClearFocus();
+
+        base.OnLostFocus(e);
+    }
+
+    protected override void OnKeyUp(KeyEventArgs e)
+    {
+        if (e.Key != Key.Enter) return;
+
+        if (SubmitCommand != null && SubmitCommand.CanExecute(SubmitCommandParameter))
+        {
+            SubmitCommand.Execute(SubmitCommandParameter);
+        }
+
+        OnSubmit?.Invoke(this, new InputBoxEventArgs(Text));
+
+        e.Handled = true;
+    }
+}

+ 11 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/InputBoxEventArgs.cs

@@ -0,0 +1,11 @@
+namespace PixiEditor.Models.Events;
+
+internal class InputBoxEventArgs : EventArgs
+{
+    public string Input { get; set; }
+
+    public InputBoxEventArgs(string input)
+    {
+        Input = input;
+    }
+}

+ 21 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/NumberInput.axaml

@@ -0,0 +1,21 @@
+<UserControl
+    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:xaml="clr-namespace:PixiEditor.Models.Commands.XAML"
+             xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours"
+             mc:Ignorable="d"
+             d:DesignHeight="20" d:DesignWidth="40" x:Name="numberInput" Focusable="True">
+    <TextBox TextAlignment="Center" Focusable="True"
+             PointerWheelChanged="TextBox_MouseWheel"
+             TextInput="TextBox_PreviewTextInput"
+             Text="{Binding ElementName=numberInput, Path=Value}"
+             Padding="0" VerticalContentAlignment="Center">
+        <Interaction.Behaviors>
+            <behaviours:TextBoxFocusBehavior SelectOnMouseClick="True" ConfirmOnEnter="True"
+                                             FocusNext="{Binding ElementName=numberInput, Path=FocusNext}"/>
+            <behaviours:GlobalShortcutFocusBehavior/>
+        </Interaction.Behaviors>
+    </TextBox>
+</UserControl>

+ 114 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/NumberInput.axaml.cs

@@ -0,0 +1,114 @@
+using System.Text.RegularExpressions;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Hardware.Info;
+
+namespace PixiEditor.Views.UserControls;
+
+internal partial class NumberInput : UserControl
+{
+    public static readonly StyledProperty<float> ValueProperty =
+        AvaloniaProperty.Register<NumberInput, float>(
+            nameof(Value), 0f);
+
+    public static readonly StyledProperty<float> MinProperty =
+        AvaloniaProperty.Register<NumberInput, float>(
+            nameof(Min), float.NegativeInfinity);
+
+    public static readonly StyledProperty<float> MaxProperty =
+        AvaloniaProperty.Register<NumberInput, float>(
+            nameof(Max), float.PositiveInfinity);
+
+    private readonly Regex regex = new Regex("^[.][0-9]+$|^[0-9]*[.]{0,1}[0-9]*$", RegexOptions.Compiled);
+
+    public int Decimals
+    {
+        get { return (int)GetValue(DecimalsProperty); }
+        set { SetValue(DecimalsProperty, value); }
+    }
+
+    public static readonly StyledProperty<int> DecimalsProperty =
+        AvaloniaProperty.Register<NumberInput, int>(nameof(Decimals), 2);
+
+    public Action OnScrollAction
+    {
+        get { return (Action)GetValue(OnScrollActionProperty); }
+        set { SetValue(OnScrollActionProperty, value); }
+    }
+
+    public static readonly StyledProperty<Action> OnScrollActionProperty =
+        AvaloniaProperty.Register<NumberInput, Action>(nameof(OnScrollAction));
+
+    static NumberInput()
+    {
+        ValueProperty.Changed.Subscribe(OnValueChanged);
+    }
+
+    public NumberInput()
+    {
+        InitializeComponent();
+    }
+
+    public float Value
+    {
+        get => (float)GetValue(ValueProperty);
+        set => SetValue(ValueProperty, value);
+    }
+
+    public float Min
+    {
+        get => (float)GetValue(MinProperty);
+        set => SetValue(MinProperty, value);
+    }
+
+    public float Max
+    {
+        get => (float)GetValue(MaxProperty);
+        set => SetValue(MaxProperty, value);
+    }
+
+    public static readonly StyledProperty<bool> FocusNextProperty =
+        AvaloniaProperty.Register<NumberInput, bool>(
+            nameof(FocusNext));
+
+    public bool FocusNext
+    {
+        get { return (bool)GetValue(FocusNextProperty); }
+        set { SetValue(FocusNextProperty, value); }
+    }
+
+    private static void OnValueChanged(AvaloniaPropertyChangedEventArgs<float> e)
+    {
+        NumberInput input = (NumberInput)d;
+        input.Value = (float)Math.Round(Math.Clamp(e.NewValue.Value, input.Min, input.Max), input.Decimals);
+    }
+
+    private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
+    {
+        e.Handled = !regex.IsMatch((sender as TextBox).Text.Insert((sender as TextBox).SelectionStart, e.Text));
+    }
+
+    private void TextBox_MouseWheel(object sender, PointerWheelEventArgs e)
+    {
+        int step = e.Delta.Length / 100;
+
+        float newValue = Value;
+        if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
+        {
+            float multiplier = (Max - Min) * 0.1f;
+            newValue += step * multiplier;
+        }
+        else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
+        {
+            newValue += step / 2f;
+        }
+        else
+        {
+            newValue += step;
+        }
+        Value = (float)Math.Round(Math.Clamp(newValue, Min, Max), Decimals);
+
+        OnScrollAction?.Invoke();
+    }
+}

+ 6 - 5
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Windows/PalettesBrowser.axaml

@@ -9,6 +9,8 @@
     xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
     xmlns:dialogs1="clr-namespace:PixiEditor.Views.Dialogs"
     xmlns:viewModels="clr-namespace:PixiEditor.Avalonia.ViewModels"
+    xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours"
+    xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
     mc:Ignorable="d"
     WindowStartupLocation="CenterScreen"
     Height="600" Width="850"
@@ -66,16 +68,15 @@
                     </Image>
                 </ToggleButton>
                 <Label Margin="10 0 0 0" ui:Translator.Key="NAME" VerticalAlignment="Center"/>
-                <usercontrols:InputBox
-                                       Text="{Binding NameFilter, Delay=100, ElementName=palettesBrowser, UpdateSourceTrigger=PropertyChanged}"
-                                       VerticalAlignment="Center"
-                                       Style="{StaticResource DarkTextBoxStyle}" Width="150">
+                <userControls:InputBox
+                                       Text="{Binding NameFilter, ElementName=palettesBrowser}"
+                                       VerticalAlignment="Center" Width="150">
                     <Interaction.Behaviors>
                         <behaviours:TextBoxFocusBehavior SelectOnMouseClick="True" ConfirmOnEnter="True"
                                                          FocusNext="{Binding ElementName=numberInput, Path=FocusNext}"/>
                         <behaviours:GlobalShortcutFocusBehavior/>
                     </Interaction.Behaviors>
-                </usercontrols:InputBox>
+                </userControls:InputBox>
 
                 <Label Margin="10 0 0 0" ui:Translator.Key="COLORS" Style="{StaticResource BaseLabel}" VerticalAlignment="Center"/>
                 <ComboBox x:Name="colorsComboBox" VerticalAlignment="Center" SelectionChanged="ColorsComboBox_SelectionChanged">

+ 1 - 1
src/PixiEditor.Extensions/Windowing/PopupWindow.cs

@@ -20,7 +20,7 @@ public class PopupWindow : IPopupWindow
     public void Show() => _underlyingWindow.Show();
     public void Close() => _underlyingWindow.Close();
 
-    public bool? ShowDialog() => _underlyingWindow.ShowDialog();
+    public Task<bool?> ShowDialog() => _underlyingWindow.ShowDialog();
     public double Width
     {
         get => _underlyingWindow.Width;