浏览代码

PaletteBrowser ported

Krzysztof Krysiński 2 年之前
父节点
当前提交
1b14f82eda
共有 24 个文件被更改,包括 565 次插入56 次删除
  1. 17 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/Converters/CountToVisibilityConverter.cs
  2. 1 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/MarkupExtensions/LocalizationExtension.cs
  3. 48 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/UI/Hyperlink.cs
  4. 4 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/PixiEditor.Avalonia.csproj
  5. 1 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/FloodFillToolViewModel.cs
  6. 1 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/LassoToolViewModel.cs
  7. 2 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/LineToolViewModel.cs
  8. 1 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/MagicWandToolViewModel.cs
  9. 2 1
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/MoveToolViewModel.cs
  10. 2 1
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/MoveViewportToolViewModel.cs
  11. 4 2
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/PenToolViewModel.cs
  12. 1 1
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/SelectToolViewModel.cs
  13. 14 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Decorators/Chip.axaml
  14. 41 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Decorators/Chip.axaml.cs
  15. 8 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Indicators/LoadingIndicator.axaml
  16. 19 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Indicators/LoadingIndicator.axaml.cs
  17. 27 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/EditableTextBlock.axaml
  18. 138 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/EditableTextBlock.axaml.cs
  19. 3 2
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/NumberInput.axaml
  20. 6 5
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/NumberInput.axaml.cs
  21. 132 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Palettes/PaletteItem.axaml
  22. 68 0
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Palettes/PaletteItem.axaml.cs
  23. 20 26
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Windows/PalettesBrowser.axaml
  24. 5 18
      src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Windows/PalettesBrowser.axaml.cs

+ 17 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/Converters/CountToVisibilityConverter.cs

@@ -0,0 +1,17 @@
+using System.Globalization;
+using System.Windows;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class CountToVisibilityConverter : SingleInstanceConverter<CountToVisibilityConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is int intVal)
+        {
+            return intVal == 0;
+        }
+
+        return true;
+    }
+}

+ 1 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/MarkupExtensions/LocalizationExtension.cs

@@ -27,6 +27,7 @@ public class LocalizationExtension : MarkupExtension
 
     private object GetFlowDirectionBinding(IServiceProvider serviceProvider)
     {
+        //TODO: Fix this
         /*flowDirectionBinding = new Binding("CurrentLanguage.FlowDirection");
         flowDirectionBinding.Source = ViewModelMain.Current.LocalizationProvider;
         flowDirectionBinding.Mode = BindingMode.OneWay;

+ 48 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/UI/Hyperlink.cs

@@ -0,0 +1,48 @@
+using System.ComponentModel;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using PixiEditor.OperatingSystem;
+
+namespace PixiEditor.Avalonia.Helpers;
+
+public class Hyperlink : AvaloniaObject
+{
+    public static AttachedProperty<string> UrlProperty
+        = AvaloniaProperty.RegisterAttached<Hyperlink, TextBlock, string>(
+            "Url");
+
+    public static string GetUrl(TextBlock element)
+    {
+        return element.GetValue(UrlProperty);
+    }
+
+    public static void SetUrl(TextBlock element, string value)
+    {
+        element.SetValue(UrlProperty, value);
+    }
+
+    static Hyperlink()
+    {
+        UrlProperty.Changed.Subscribe(OnUrlSet);
+    }
+
+    private static void OnUrlSet(AvaloniaPropertyChangedEventArgs e)
+    {
+        if (e.Sender is TextBlock tb)
+        {
+            tb.Classes.Add("link");
+            tb.Cursor = new Cursor(StandardCursorType.Hand);
+            tb.PointerPressed += (sender, args) =>
+            {
+                if (sender is TextBlock tb)
+                {
+                    if (tb.GetValue(UrlProperty) is string url)
+                    {
+                        IOperatingSystem.Current.OpenHyperlink(url);
+                    }
+                }
+            };
+        }
+    }
+}

+ 4 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/PixiEditor.Avalonia.csproj

@@ -51,4 +51,8 @@
     <ItemGroup>
       <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
     </ItemGroup>
+  
+    <ItemGroup>
+      <Folder Include="Views\Buttons\" />
+    </ItemGroup>
 </Project>

+ 1 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/FloodFillToolViewModel.cs

@@ -1,4 +1,5 @@
 using System.Windows.Input;
+using Avalonia.Input;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Common.Localization;

+ 1 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/LassoToolViewModel.cs

@@ -1,4 +1,5 @@
 using System.Windows.Input;
+using Avalonia.Input;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Common.Localization;

+ 2 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/LineToolViewModel.cs

@@ -1,8 +1,10 @@
 using System.Windows.Input;
+using Avalonia.Input;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Models.Containers.Tools;
 using PixiEditor.Models.Localization;
 using PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Toolbars;
 

+ 1 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/MagicWandToolViewModel.cs

@@ -1,4 +1,5 @@
 using System.Windows.Input;
+using Avalonia.Input;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Common.Localization;

+ 2 - 1
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/MoveToolViewModel.cs

@@ -1,4 +1,5 @@
 using System.Windows.Input;
+using Avalonia.Input;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Common.Localization;
@@ -24,7 +25,7 @@ internal class MoveToolViewModel : ToolViewModel
     {
         ActionDisplay = defaultActionDisplay;
         Toolbar = ToolbarFactory.Create(this);
-        Cursor = Cursors.Arrow;
+        Cursor = new Cursor(StandardCursorType.Arrow);
     }
 
     public override LocalizedString Tooltip => new LocalizedString("MOVE_TOOL_TOOLTIP", Shortcut);

+ 2 - 1
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/MoveViewportToolViewModel.cs

@@ -1,4 +1,5 @@
 using System.Windows.Input;
+using Avalonia.Input;
 using PixiEditor.Extensions.Common.Localization;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Localization;
@@ -16,7 +17,7 @@ internal class MoveViewportToolViewModel : ToolViewModel
 
     public MoveViewportToolViewModel()
     {
-        Cursor = Cursors.SizeAll;
+        Cursor = new Cursor(StandardCursorType.SizeAll);
     }
 
     public override void OnSelected()

+ 4 - 2
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/PenToolViewModel.cs

@@ -1,5 +1,7 @@
-using System.Windows.Input;
+using System.Linq;
+using System.Windows.Input;
 using System.Windows.Media;
+using Avalonia.Input;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Common.Localization;
@@ -24,7 +26,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools
 
         public PenToolViewModel()
         {
-            Cursor = Cursors.Pen;
+            Cursor = new Cursor(StandardCursorType.Help); // TODO: Create pen cursor
             Toolbar = ToolbarFactory.Create<PenToolViewModel, BasicToolbar>(this);
             
             ViewModelMain.Current.ToolsSubViewModel.SelectedToolChanged += SelectedToolChanged;

+ 1 - 1
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/Tools/SelectToolViewModel.cs

@@ -23,7 +23,7 @@ internal class SelectToolViewModel : ToolViewModel
     {
         ActionDisplay = defaultActionDisplay;
         Toolbar = ToolbarFactory.Create(this);
-        Cursor = Cursors.Cross;
+        Cursor = new Cursor(StandardCursorType.Cross);
     }
 
     private SelectionMode KeyModifierselectionMode = SelectionMode.New;

+ 14 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Decorators/Chip.axaml

@@ -0,0 +1,14 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.Chip"
+             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"
+             mc:Ignorable="d" x:Name="chip"
+             d:DesignHeight="30" d:DesignWidth="60">
+    <Border VerticalAlignment="Center" Background="{StaticResource GlyphBrush}" BorderThickness="1"
+            BorderBrush="{Binding ElementName=chip, Path=OutlineColor}"
+            Padding="5 2.5" CornerRadius="2.5">
+        <TextBlock Text="{Binding ElementName=chip, Path=Text}" Foreground="White"/>
+    </Border>
+</UserControl>

+ 41 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Decorators/Chip.axaml.cs

@@ -0,0 +1,41 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+
+namespace PixiEditor.Views.UserControls;
+
+internal partial class Chip : UserControl, ICustomTranslatorElement
+{
+    public static readonly StyledProperty<string> TextProperty =
+        AvaloniaProperty.Register<Chip, string>(nameof(Text));
+
+    public string Text
+    {
+        get { return (string)GetValue(TextProperty); }
+        set { SetValue(TextProperty, value); }
+    }
+
+    public static readonly StyledProperty<SolidColorBrush> OutlineColorProperty =
+        AvaloniaProperty.Register<Chip, SolidColorBrush>(nameof(OutlineColor));
+
+    public SolidColorBrush OutlineColor
+    {
+        get { return (SolidColorBrush)GetValue(OutlineColorProperty); }
+        set { SetValue(OutlineColorProperty, value); }
+    }
+    public Chip()
+    {
+        InitializeComponent();
+    }
+
+    public void SetTranslationBinding(AvaloniaProperty dependencyProperty, IObservable<string> binding)
+    {
+        Bind(dependencyProperty, binding);
+    }
+
+    public AvaloniaProperty GetDependencyProperty()
+    {
+        return TextProperty;
+    }
+}
+

+ 8 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Indicators/LoadingIndicator.axaml

@@ -0,0 +1,8 @@
+<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"
+             mc:Ignorable="d" d:DesignWidth="50" d:DesignHeight="50"
+             x:Class="PixiEditor.Avalonia.Views.Indicators.LoadingIndicator">
+    <Image Source="avares://PixiEditor.UI.Common/Assets/Processing.gif"/>
+</UserControl>

+ 19 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Indicators/LoadingIndicator.axaml.cs

@@ -0,0 +1,19 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace PixiEditor.Avalonia.Views.Indicators;
+
+public partial class LoadingIndicator : UserControl
+{
+    public LoadingIndicator()
+    {
+        InitializeComponent();
+    }
+
+    private void InitializeComponent()
+    {
+        AvaloniaXamlLoader.Load(this);
+    }
+}
+

+ 27 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/EditableTextBlock.axaml

@@ -0,0 +1,27 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.EditableTextBlock"
+             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.Helpers.Behaviours"
+             mc:Ignorable="d" x:Name="etb"
+             d:DesignHeight="60" d:DesignWidth="100" Focusable="True">
+    <Grid>
+        <TextBlock Foreground="{Binding ElementName=etb, Path=Foreground}" PointerPressed="TextBlock_MouseDown"
+                   TextTrimming="CharacterEllipsis" Name="textBlock"
+                   IsVisible="{Binding Path=TextBlockVisibility, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
+                   Text="{Binding Path=Text, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Mode=TwoWay}" />
+        <TextBox Foreground="{Binding ElementName=etb, Path=Foreground}"
+                 MaxLength="{Binding Path=MaxChars, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Mode=TwoWay}"
+                 LostFocus="TextBox_LostFocus"
+                 Text="{Binding Path=Text, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Mode=TwoWay}"
+                 KeyDown="TextBox_KeyDown"
+                 IsVisible="{Binding Path=!TextBlockVisibility, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
+                 Name="textBox">
+            <Interaction.Behaviors>
+                <behaviours:GlobalShortcutFocusBehavior/>
+            </Interaction.Behaviors>
+        </TextBox>
+    </Grid>
+</UserControl>

+ 138 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/EditableTextBlock.axaml.cs

@@ -0,0 +1,138 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using Avalonia.Threading;
+using Hardware.Info;
+using PixiEditor.Models.Controllers;
+
+namespace PixiEditor.Views.UserControls;
+
+internal partial class EditableTextBlock : UserControl
+{
+    public static readonly StyledProperty<bool> TextBlockVisibilityProperty =
+        AvaloniaProperty.Register<EditableTextBlock, bool>(
+            nameof(TextBlockVisibility));
+
+
+    public static readonly StyledProperty<string> TextProperty =
+        AvaloniaProperty.Register<EditableTextBlock, string>(
+            nameof(Text));
+
+
+    public static readonly StyledProperty<bool> EnableEditingProperty =
+        AvaloniaProperty.Register<EditableTextBlock, bool>(
+            nameof(IsEditing));
+
+    public int MaxChars
+    {
+        get { return (int)GetValue(MaxCharsProperty); }
+        set { SetValue(MaxCharsProperty, value); }
+    }
+
+
+    public static readonly StyledProperty<int> MaxCharsProperty =
+        AvaloniaProperty.Register<EditableTextBlock, int>(nameof(MaxChars), int.MaxValue);
+
+    public static readonly StyledProperty<SolidColorBrush> ForegroundProperty =
+        AvaloniaProperty.Register<EditableTextBlock, SolidColorBrush>(
+        nameof(Foreground), new SolidColorBrush(Brushes.White.Color));
+
+    public SolidColorBrush Foreground
+    {
+        get { return (SolidColorBrush)GetValue(ForegroundProperty); }
+        set { SetValue(ForegroundProperty, value); }
+    }
+
+    public event EventHandler<TextChangedEventArgs> OnSubmit;
+
+    static EditableTextBlock()
+    {
+        EnableEditingProperty.Changed.Subscribe(OnIsEditingChanged);
+    }
+
+    public EditableTextBlock()
+    {
+        InitializeComponent();
+    }
+
+    public bool TextBlockVisibility
+    {
+        get => (bool)GetValue(TextBlockVisibilityProperty);
+        set => SetValue(TextBlockVisibilityProperty, value);
+    }
+
+    public bool IsEditing
+    {
+        get => (bool)GetValue(EnableEditingProperty);
+        set => SetValue(EnableEditingProperty, value);
+    }
+
+    public string Text
+    {
+        get => (string)GetValue(TextProperty);
+        set => SetValue(TextProperty, value);
+    }
+
+    public void EnableEditing()
+    {
+        ShortcutController.BlockShortcutExecution("EditableTextBlock");
+        TextBlockVisibility = false;
+        IsEditing = true;
+        //TODO: Note Previously there was a dispatcher and keyboard focus.
+        textBox.Focus();         // Set Logical Focus
+        textBox.SelectAll();
+    }
+
+    public void DisableEditing()
+    {
+        TextBlockVisibility = true;
+        ShortcutController.UnblockShortcutExecution("EditableTextBlock");
+        IsEditing = false;
+        OnSubmit?.Invoke(this, new TextChangedEventArgs(textBox.Text, Text));
+    }
+
+    private static void OnIsEditingChanged(AvaloniaPropertyChangedEventArgs<bool> e)
+    {
+        if (e.NewValue.Value)
+        {
+            EditableTextBlock tb = (EditableTextBlock)e.Sender;
+            tb.EnableEditing();
+        }
+    }
+
+    private void TextBlock_MouseDown(object? sender, PointerPressedEventArgs e)
+    {
+        if (e.ClickCount == 2)
+        {
+            EnableEditing();
+        }
+    }
+
+    private void TextBox_KeyDown(object sender, KeyEventArgs e)
+    {
+        if (e.Key == Key.Enter)
+        {
+            DisableEditing();
+        }
+    }
+
+    private void TextBox_LostFocus(object? sender, RoutedEventArgs routedEventArgs)
+    {
+        DisableEditing();
+    }
+
+    internal class TextChangedEventArgs : EventArgs
+    {
+        public string NewText { get; set; }
+
+        public string OldText { get; set; }
+
+        public TextChangedEventArgs(string newText, string oldText)
+        {
+            NewText = newText;
+            OldText = oldText;
+        }
+    }
+}

+ 3 - 2
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/NumberInput.axaml

@@ -1,10 +1,11 @@
 <UserControl
+    x:Class="PixiEditor.Views.UserControls.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:xaml="clr-namespace:PixiEditor.Models.Commands.XAML"
-             xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours"
+    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"

+ 6 - 5
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/NumberInput.axaml.cs

@@ -80,26 +80,26 @@ internal partial class NumberInput : UserControl
 
     private static void OnValueChanged(AvaloniaPropertyChangedEventArgs<float> e)
     {
-        NumberInput input = (NumberInput)d;
+        NumberInput input = (NumberInput)e.Sender;
         input.Value = (float)Math.Round(Math.Clamp(e.NewValue.Value, input.Min, input.Max), input.Decimals);
     }
 
-    private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
+    private void TextBox_PreviewTextInput(object sender, TextInputEventArgs 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;
+        int step = (int)e.Delta.Y / 100;
 
         float newValue = Value;
-        if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
+        if (e.KeyModifiers.HasFlag(KeyModifiers.Shift))
         {
             float multiplier = (Max - Min) * 0.1f;
             newValue += step * multiplier;
         }
-        else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
+        else if (e.KeyModifiers.HasFlag(KeyModifiers.Control))
         {
             newValue += step / 2f;
         }
@@ -107,6 +107,7 @@ internal partial class NumberInput : UserControl
         {
             newValue += step;
         }
+
         Value = (float)Math.Round(Math.Clamp(newValue, Min, Max), Decimals);
 
         OnScrollAction?.Invoke();

+ 132 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Palettes/PaletteItem.axaml

@@ -0,0 +1,132 @@
+<UserControl
+    x:Class="PixiEditor.Views.UserControls.Palettes.PaletteItem"
+             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:views="clr-namespace:PixiEditor.Views"
+    xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
+             xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+             mc:Ignorable="d"
+             d:DesignHeight="200"
+             d:DesignWidth="800" 
+             Name="paletteItem">
+    <Grid Background="{DynamicResource ThemeBackgroundBrush}" >
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="100*"/>
+            <ColumnDefinition Width="95"/>
+        </Grid.ColumnDefinitions>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="60"/>
+            <RowDefinition Height="60*" MinHeight="45"/>
+        </Grid.RowDefinitions>
+        <StackPanel Orientation="Vertical" Grid.RowSpan="2" Grid.ColumnSpan="2">
+            <Separator Background="{StaticResource MainColor}" />
+            <DockPanel>
+                <StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
+                    <userControls:EditableTextBlock x:Name="titleTextBlock" OnSubmit="EditableTextBlock_OnSubmit"
+                                                    Text="{Binding Palette.Name, ElementName=paletteItem, Mode=TwoWay}"
+                                                    FontSize="20" MaxChars="50"/>
+                <Button IsVisible="{Binding ElementName=paletteItem, Path=IsMouseOver}"
+                        Click="RenameButton_Click"
+                        Cursor="Hand" Width="20" Height="20">
+                    <Image Source="/Images/Edit.png"/>
+                </Button>
+            </StackPanel>
+                <!--<Image Margin="0 5 5 0"
+                       Source="/Images/SupperterPack.png" Width="24"
+                       DockPanel.Dock="Right" HorizontalAlignment="Right"/>-->
+                <userControls:Chip Margin="0 5 5 0"
+                                   ui:Translator.Key="{Binding ElementName=paletteItem, Path=Palette.Source.Name.Key}"
+                                   DockPanel.Dock="Right" HorizontalAlignment="Right"/>
+            </DockPanel>
+            <TextBlock Margin="0 5 0 0">
+            </TextBlock>
+        </StackPanel>
+        <ItemsControl Margin="0 -20 0 10" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding ElementName=paletteItem, Path=Palette.Colors}">
+            <ItemsControl.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <WrapPanel Orientation="Horizontal" IsItemsHost="True"/>
+                </ItemsPanelTemplate>
+            </ItemsControl.ItemsPanel>
+            <ItemsControl.ItemTemplate>
+                <DataTemplate>
+                    <Rectangle Fill="{Binding Hex}" ToolTip.Tip="{Binding}" Width="30" Height="30"/>
+                </DataTemplate>
+            </ItemsControl.ItemTemplate>
+        </ItemsControl>
+        <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" VerticalAlignment="Center">
+            <Border Margin="0 0 2 0" Width="28" Height="28" HorizontalAlignment="Right" CornerRadius="2.5">
+                <Border.Styles>
+                    <Style Selector="Border">
+                        <Setter Property="Background" Value="Transparent"/>
+                    </Style>
+                    <Style Selector="Border:hover">
+                        <Setter Property="Background" Value="SeaGreen"/>
+                    </Style>
+                </Border.Styles>
+                <Button
+                    ui:Translator.TooltipKey="USE_IN_CURRENT_IMAGE" Cursor="Hand"
+                    Margin="0 3 0 0" Width="24" Height="24"
+                    CommandParameter="{Binding ElementName=paletteItem, Path=Palette.Colors}"
+                    Command="{Binding ImportPaletteCommand, ElementName=paletteItem}">
+                    <Button.Background>
+                        <ImageBrush Source="/Images/Check-square.png"/>
+                    </Button.Background>
+                </Button>
+            </Border>
+            <Border Margin="2 0 -2 0" Width="28" Height="28" HorizontalAlignment="Right" CornerRadius="2.5"
+                    Padding="2">
+                <Border.Styles>
+                    <Style Selector="Border">
+                        <Setter Property="Background" Value="Transparent"/>
+                    </Style>
+                    <Style Selector="Border:hover">
+                        <Setter Property="Background" Value="DarkOrange"/>
+                    </Style>
+                </Border.Styles>
+                <Button
+                    Classes="ImageButton"
+                    Command="{Binding ElementName=paletteItem, Path=ToggleFavouriteCommand}"
+                    CommandParameter="{Binding ElementName=paletteItem, Path=Palette}"
+                    ui:Translator.TooltipKey="ADD_TO_FAVORITES">
+                    <Button.Styles>
+                        <Style Selector="Button">
+                            <Setter Property="Background">
+                                <Setter.Value>
+                                    <ImageBrush Source="/Images/Star.png"/>
+                                </Setter.Value>
+                            </Setter>
+                            </Style>
+                        <Style Selector="Button.IsFavourite">
+                            <Setter Property="Background">
+                                <Setter.Value>
+                                    <ImageBrush Source="/Images/Star-filled.png"/>
+                                </Setter.Value>
+                            </Setter>
+                        </Style>
+                    </Button.Styles>
+                </Button>
+            </Border>
+            <Border Width="28" Height="28" CornerRadius="2.5"
+                    Margin="5 0 0 0" Padding="2" Name="deleteBorder">
+                <Border.Styles>
+                    <Style Selector="Border">
+                        <Setter Property="Background" Value="Transparent"/>
+                    </Style>
+                    <Style Selector="Border.IsEnabled:hover">
+                        <Setter Property="Background" Value="Red"/>
+                    </Style>
+                </Border.Styles>
+                <Button Name="deleteButton" Command="{Binding DeletePaletteCommand, ElementName=paletteItem}"
+                        CommandParameter="{Binding ElementName=paletteItem, Path=Palette}"
+                ui:Translator.TooltipKey="DELETE" Width="24" Height="24" Margin="0" Cursor="Hand">
+                    <Button.Background>
+                        <ImageBrush Source="/Images/Trash.png"/>
+                    </Button.Background>
+                </Button>
+            </Border>
+        </StackPanel>
+    </Grid>
+</UserControl>

+ 68 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Palettes/PaletteItem.axaml.cs

@@ -0,0 +1,68 @@
+using System.Windows.Input;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using PixiEditor.Models.DataHolders.Palettes;
+
+namespace PixiEditor.Views.UserControls.Palettes;
+
+internal partial class PaletteItem : UserControl
+{
+    public Palette Palette
+    {
+        get { return (Palette)GetValue(PaletteProperty); }
+        set { SetValue(PaletteProperty, value); }
+    }
+
+    public static readonly StyledProperty<Palette> PaletteProperty =
+        AvaloniaProperty.Register<PaletteItem, Palette>(
+            nameof(Palette));
+
+    public ICommand ImportPaletteCommand
+    {
+        get { return (ICommand)GetValue(ImportPaletteCommandProperty); }
+        set { SetValue(ImportPaletteCommandProperty, value); }
+    }
+
+    public static readonly StyledProperty<ICommand> ImportPaletteCommandProperty =
+        AvaloniaProperty.Register<PaletteItem, ICommand>(
+            nameof(ImportPaletteCommand));
+
+    public ICommand DeletePaletteCommand
+    {
+        get { return (ICommand)GetValue(DeletePaletteCommandProperty); }
+        set { SetValue(DeletePaletteCommandProperty, value); }
+    }
+
+    public static readonly StyledProperty<ICommand> DeletePaletteCommandProperty =
+        AvaloniaProperty.Register<PaletteItem, ICommand>(
+            nameof(DeletePaletteCommand));
+
+    public static readonly StyledProperty<ICommand> ToggleFavouriteCommandProperty =
+        AvaloniaProperty.Register<PaletteItem, ICommand>(
+        nameof(ToggleFavouriteCommand));
+
+    public ICommand ToggleFavouriteCommand
+    {
+        get { return (ICommand)GetValue(ToggleFavouriteCommandProperty); }
+        set { SetValue(ToggleFavouriteCommandProperty, value); }
+    }
+
+    public event EventHandler<EditableTextBlock.TextChangedEventArgs> OnRename;
+
+
+    public PaletteItem()
+    {
+        InitializeComponent();
+    }
+
+    private void EditableTextBlock_OnSubmit(object sender, EditableTextBlock.TextChangedEventArgs e)
+    {
+        OnRename?.Invoke(this, e);
+    }
+
+    private void RenameButton_Click(object sender, RoutedEventArgs e)
+    {
+        titleTextBlock.IsEditing = true;
+    }
+}

+ 20 - 26
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Windows/PalettesBrowser.axaml

@@ -11,6 +11,10 @@
     xmlns:viewModels="clr-namespace:PixiEditor.Avalonia.ViewModels"
     xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours"
     xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
+    xmlns:palettes="clr-namespace:PixiEditor.Views.UserControls.Palettes"
+    xmlns:helpers1="clr-namespace:PixiEditor.Avalonia.Helpers"
+    xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+    xmlns:indicators="clr-namespace:PixiEditor.Avalonia.Views.Indicators"
     mc:Ignorable="d"
     WindowStartupLocation="CenterScreen"
     Height="600" Width="850"
@@ -44,26 +48,17 @@
                               Focusable="False">
                     <Image Width="24" Height="24" Source="/Images/ChevronsDown.png">
                         <Image.Styles>
-                            <Style Selector="{x:Type Image}">
+                            <Style Selector="Image">
+                                <Setter Property="ui:Translator.TooltipKey" Value="DESCENDING"/>
+                            </Style>
+                            <Style Selector="Image.IsChecked">
                                 <Setter Property="RenderTransform">
                                     <Setter.Value>
-
+                                        <RotateTransform Angle="180" CenterX="11.5" CenterY="11.5"/>
                                     </Setter.Value>
                                 </Setter>
-                                <Style.Triggers>
-                                    <DataTrigger Binding="{Binding IsChecked, ElementName=toggleBtn}" Value="true">
-                                        <Setter Property="RenderTransform">
-                                            <Setter.Value>
-                                                <RotateTransform Angle="180" CenterX="11.5" CenterY="11.5"/>
-                                            </Setter.Value>
-                                        </Setter>
-                                        <Setter Property="ui:Translator.TooltipKey" Value="ASCENDING"/>
-                                    </DataTrigger>
-                                    <DataTrigger Binding="{Binding IsChecked, ElementName=toggleBtn}" Value="false">
-                                        <Setter Property="ui:Translator.TooltipKey" Value="DESCENDING"/>
-                                    </DataTrigger>
-                                </Style.Triggers>
-                            </Style>
+                                <Setter Property="ui:Translator.TooltipKey" Value="ASCENDING"/>
+                                </Style>
                         </Image.Styles>
                     </Image>
                 </ToggleButton>
@@ -78,14 +73,14 @@
                     </Interaction.Behaviors>
                 </userControls:InputBox>
 
-                <Label Margin="10 0 0 0" ui:Translator.Key="COLORS" Style="{StaticResource BaseLabel}" VerticalAlignment="Center"/>
+                <Label Margin="10 0 0 0" ui:Translator.Key="COLORS" VerticalAlignment="Center"/>
                 <ComboBox x:Name="colorsComboBox" VerticalAlignment="Center" SelectionChanged="ColorsComboBox_SelectionChanged">
                     <ComboBoxItem IsSelected="True" ui:Translator.Key="ANY"/>
                     <ComboBoxItem ui:Translator.Key="MAX"/>
                     <ComboBoxItem ui:Translator.Key="MIN"/>
                     <ComboBoxItem ui:Translator.Key="EXACT"/>
                 </ComboBox>
-                <usercontrols:NumberInput Width="50" VerticalAlignment="Center" Margin="10 0 0 0"
+                <userControls:NumberInput Width="50" VerticalAlignment="Center" Margin="10 0 0 0"
                                    FocusNext="True"
                                    Value="{Binding ColorsNumber, ElementName=palettesBrowser, Mode=TwoWay}"/>
                 <CheckBox Margin="10 0 0 0" VerticalAlignment="Center"
@@ -120,10 +115,8 @@
                         IsVisible="{Binding ElementName=palettesBrowser, Path=SortedResults.Count, Converter={converters:CountToVisibilityConverter}}">
                 <TextBlock ui:Translator.Key="NO_PALETTES_FOUND" Foreground="White" FontSize="20" TextAlignment="Center"/>
                 <TextBlock Margin="0 10 0 0">
-                    <Hyperlink Foreground="Gray" Cursor="Hand" FontSize="18" NavigateUri="https://lospec.com/palette-list"
-                               RequestNavigate="Hyperlink_OnRequestNavigate">
-                        <TextBlock ui:Translator.Key="LOSPEC_LINK_TEXT"/>
-                    </Hyperlink>
+                    <TextBlock ui:Translator.Key="LOSPEC_LINK_TEXT"
+                               helpers1:Hyperlink.Url="https://lospec.com/palette-list"/>
                 </TextBlock>
                 <Image Width="128" Height="128" Source="/Images/Search.png"/>
             </StackPanel>
@@ -132,7 +125,7 @@
                               IsVisible="{Binding PaletteList.FetchedCorrectly, ElementName=palettesBrowser}">
                     <ItemsControl.ItemTemplate>
                         <DataTemplate>
-                            <local:PaletteItem Palette="{Binding}"
+                            <palettes:PaletteItem Palette="{Binding}"
                                                OnRename="PaletteItem_OnRename"
                                                ToggleFavouriteCommand="{Binding ToggleFavouriteCommand, ElementName=palettesBrowser}"
                                                DeletePaletteCommand="{Binding DeletePaletteCommand, ElementName=palettesBrowser}"
@@ -141,9 +134,10 @@
                     </ItemsControl.ItemTemplate>
                 </ItemsControl>
             </ScrollViewer>
-            <Image gif:ImageBehavior.AnimatedSource="/Images/Processing.gif" HorizontalAlignment="Center" VerticalAlignment="Center"
-                   IsVisible="{Binding IsFetching, ElementName=palettesBrowser}"
-                   Height="50" gif:ImageBehavior.AnimationSpeedRatio="1.5"/>
+            <indicators:LoadingIndicator
+                IsVisible="{Binding IsFetching, ElementName=palettesBrowser}"
+                HorizontalAlignment="Center" VerticalAlignment="Center"
+                Height="50"/>
         </Grid>
     </Grid>
 </Window>

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

@@ -27,6 +27,8 @@ using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.IO;
 using PixiEditor.OperatingSystem;
+using PixiEditor.Views.UserControls;
+using PixiEditor.Views.UserControls.Palettes;
 using PaletteColor = PixiEditor.Extensions.Palettes.PaletteColor;
 
 namespace PixiEditor.Views.Dialogs;
@@ -394,11 +396,6 @@ internal partial class PalettesBrowser : Window, IPopupWindow
         }
     }
 
-    private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
-    {
-        e.CanExecute = true;
-    }
-
     private static async void OnShowOnlyFavouritesChanged(AvaloniaPropertyChangedEventArgs<bool> e)
     {
         PalettesBrowser browser = (PalettesBrowser)e.Sender;
@@ -406,11 +403,6 @@ internal partial class PalettesBrowser : Window, IPopupWindow
         await browser.UpdatePaletteList();
     }
 
-    private void CommandBinding_Executed_Close(object sender, ExecutedRoutedEventArgs e)
-    {
-        SystemCommands.CloseWindow(this);
-    }
-
     private static async void ColorsNumberChanged(AvaloniaPropertyChangedEventArgs<int> e)
     {
         PalettesBrowser browser = (PalettesBrowser)e.Sender;
@@ -485,7 +477,7 @@ internal partial class PalettesBrowser : Window, IPopupWindow
     {
         if (PaletteList?.Palettes == null) return;
         var viewer = (ScrollViewer)sender;
-        if (viewer.VerticalOffset == viewer.ScrollableHeight && _lastScrolledOffset != viewer.VerticalOffset)
+        if (viewer.Offset.Y == viewer.Viewport.Height && _lastScrolledOffset != viewer.Offset.Y)
         {
             IsFetching = true;
             var newPalettes = await FetchPaletteList(Filtering);
@@ -499,7 +491,7 @@ internal partial class PalettesBrowser : Window, IPopupWindow
             Sort();
             IsFetching = false;
 
-            _lastScrolledOffset = viewer.VerticalOffset;
+            _lastScrolledOffset = viewer.Offset.Y;
         }
     }
 
@@ -609,7 +601,7 @@ internal partial class PalettesBrowser : Window, IPopupWindow
         await LocalPalettesFetcher.SavePalette(finalFileName, CurrentEditingPalette.ToArray());
     }
 
-    private void PaletteItem_OnRename(object sender, EditableTextBlock.TextChangedEventArgs e)
+    private void PaletteItem_OnRename(object? sender, EditableTextBlock.TextChangedEventArgs e)
     {
         PaletteItem item = (PaletteItem)sender;
         item.Palette.Name = e.OldText;
@@ -684,11 +676,6 @@ internal partial class PalettesBrowser : Window, IPopupWindow
         }
     }
 
-    private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
-    {
-        IOperatingSystem.Current.OpenHyperlink(e.Uri.ToString());
-    }
-
     protected override void OnClosing(WindowClosingEventArgs e)
     {
         base.OnClosing(e);