ソースを参照

SizeInput wip

Krzysztof Krysiński 2 年 前
コミット
332a38aeb8

+ 28 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/Converters/EnumToStringConverter.cs

@@ -0,0 +1,28 @@
+using PixiEditor.Models.Enums;
+using System;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class EnumToStringConverter : SingleInstanceConverter<EnumToStringConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+    {
+        try
+        {
+            var type = value.GetType();
+            if (type == typeof(SizeUnit))
+            {
+                var valueCasted = (SizeUnit)value;
+                if (valueCasted == SizeUnit.Percentage)
+                    return "%";
+
+                return "PIXEL_UNIT";
+            }
+            return Enum.GetName((value.GetType()), value);
+        }
+        catch
+        {
+            return string.Empty;
+        }
+    }
+}

+ 26 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/Converters/EqualityBoolToIsVisibleConverter.cs

@@ -0,0 +1,26 @@
+using System.Globalization;
+using System.Windows;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class EqualityBoolToIsVisibleConverter : MarkupConverter
+{
+    public bool Invert { get; set; }
+
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value == null)
+            return Invert;
+
+        if (value.GetType().IsAssignableTo(typeof(Enum)) && parameter is string s)
+        {
+            parameter = Enum.Parse(value.GetType(), s);
+        }
+        else
+        {
+            parameter = System.Convert.ChangeType(parameter, value.GetType());
+        }
+
+        return value.Equals(parameter) != Invert ? true : false;
+    }
+}

+ 23 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/Converters/SingleInstanceMultiValueConverter.cs

@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using System.Globalization;
+using Avalonia.Data.Converters;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal abstract class SingleInstanceMultiValueConverter<TThis> : MarkupConverter, IMultiValueConverter
+    where TThis : SingleInstanceMultiValueConverter<TThis>
+{
+    private static SingleInstanceMultiValueConverter<TThis> instance;
+
+    public override object ProvideValue(IServiceProvider serviceProvider)
+    {
+        if (instance is null)
+        {
+            instance = this;
+        }
+
+        return instance;
+    }
+
+    public abstract object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture);
+}

+ 36 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/Converters/ToolSizeToIntConverter.cs

@@ -0,0 +1,36 @@
+using System.Globalization;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class ToolSizeToIntConverter
+    : SingleInstanceConverter<ToolSizeToIntConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        return value.ToString();
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is not string s)
+        {
+            return null;
+        }
+
+        Match match = Regex.Match(s, @"\d+");
+
+        if (!match.Success)
+        {
+            return null;
+        }
+
+        if (int.TryParse(match.Groups[0].ValueSpan.ToString().Normalize(NormalizationForm.FormKC), out int result))
+        {
+            return result;
+        }
+
+        return null;
+    }
+}

+ 29 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/Converters/WidthToBitmapScalingModeConverter.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Media;
+using Avalonia;
+using Avalonia.Media.Imaging;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class WidthToBitmapScalingModeConverter : SingleInstanceMultiValueConverter<WidthToBitmapScalingModeConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        return Convert(new [] {value}, targetType, parameter, culture);
+    }
+
+    public override object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
+    {
+        int? pixelWidth = values[0] as int?;
+        double? actualWidth = values[1] as double?;
+        if (pixelWidth == null || actualWidth == null)
+            return AvaloniaProperty.UnsetValue;
+        double zoomLevel = actualWidth.Value / pixelWidth.Value;
+        if (zoomLevel < 1)
+            return BitmapInterpolationMode.HighQuality;
+        return BitmapInterpolationMode.None;
+    }
+}

+ 15 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Helpers/Extensions/BitmapExtensions.cs

@@ -1,6 +1,7 @@
 using System.Runtime.InteropServices;
 using Avalonia;
 using Avalonia.Media.Imaging;
+using Avalonia.Platform;
 
 namespace PixiEditor.Avalonia.Helpers.Extensions;
 
@@ -24,4 +25,18 @@ public static class BitmapExtensions
         source.CopyPixels(new PixelRect(0, 0, size.Width, size.Height), address, bufferSize, stride);
         return target;
     }
+
+    public static WriteableBitmap ToWriteableBitmap(this Bitmap source)
+    {
+        var size = source.PixelSize;
+        var stride = size.Width * 4;
+        int bufferSize = stride * size.Height;
+
+        byte[] target = new byte[bufferSize];
+
+        var address = Marshal.UnsafeAddrOfPinnedArrayElement(target, 0);
+
+        source.CopyPixels(new PixelRect(0, 0, size.Width, size.Height), address, bufferSize, stride);
+        return new WriteableBitmap(PixelFormat.Bgra8888, AlphaFormat.Premul, address, size, new Vector(96, 96), stride);
+    }
 }

+ 14 - 9
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Models/UserData/RecentlyOpenedDocument.cs

@@ -1,7 +1,10 @@
 using System.Diagnostics;
 using System.IO;
+using Avalonia;
+using Avalonia.Media.Imaging;
 using CommunityToolkit.Mvvm.ComponentModel;
 using PixiEditor.Avalonia.Exceptions.Exceptions;
+using PixiEditor.Avalonia.Helpers.Extensions;
 using PixiEditor.Parser.Deprecated;
 using PixiEditor.Helpers;
 using PixiEditor.Parser;
@@ -15,7 +18,7 @@ internal class RecentlyOpenedDocument : ObservableObject
 
     private string filePath;
 
-    private SKBitmap previewBitmap;
+    private WriteableBitmap previewBitmap;
 
     public string FilePath
     {
@@ -56,7 +59,7 @@ internal class RecentlyOpenedDocument : ObservableObject
         }
     }
 
-    public SKBitmap PreviewBitmap
+    public WriteableBitmap PreviewBitmap
     {
         get
         {
@@ -75,7 +78,7 @@ internal class RecentlyOpenedDocument : ObservableObject
         FilePath = path;
     }
 
-    private SKBitmap LoadPreviewBitmap()
+    private WriteableBitmap LoadPreviewBitmap()
     {
         if (!File.Exists(FilePath))
         {
@@ -96,7 +99,7 @@ internal class RecentlyOpenedDocument : ObservableObject
                 }
 
                 using var data = new MemoryStream(document.PreviewImage);
-                return SKBitmap.Decode(data);
+                return WriteableBitmap.Decode(data);
             }
             catch
             {
@@ -125,7 +128,7 @@ internal class RecentlyOpenedDocument : ObservableObject
 
         if (SupportedFilesHelper.IsExtensionSupported(FileExtension))
         {
-            SKBitmap bitmap = null;
+            WriteableBitmap bitmap = null;
 
             try
             {
@@ -147,12 +150,14 @@ internal class RecentlyOpenedDocument : ObservableObject
         return null;
     }
 
-    private SKBitmap DownscaleToMaxSize(SKBitmap bitmap)
+    private WriteableBitmap DownscaleToMaxSize(WriteableBitmap bitmap)
     {
-        if (bitmap.Width > Constants.MaxPreviewWidth || bitmap.Height > Constants.MaxPreviewHeight)
+        if (bitmap.PixelSize.Width > Constants.MaxPreviewWidth || bitmap.PixelSize.Height > Constants.MaxPreviewHeight)
         {
-            double factor = Math.Min(Constants.MaxPreviewWidth / (double)bitmap.Width, Constants.MaxPreviewHeight / (double)bitmap.Height);
-            return bitmap.Resize(new SKSizeI((int)(bitmap.Width * factor), (int)(bitmap.Height * factor)), SKFilterQuality.High);
+            double factor = Math.Min(Constants.MaxPreviewWidth / (double)bitmap.PixelSize.Width, Constants.MaxPreviewHeight / (double)bitmap.PixelSize.Height);
+            var scaledBitmap = bitmap.CreateScaledBitmap(new PixelSize((int)(bitmap.PixelSize.Width * factor), (int)(bitmap.PixelSize.Height * factor)),
+                BitmapInterpolationMode.HighQuality);
+            return scaledBitmap.ToWriteableBitmap();
         }
 
         return bitmap;

+ 2 - 2
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Styles/PortingWipStyles.axaml

@@ -1,4 +1,4 @@
-<Styles xmlns="https://github.com/avaloniaui"
+<ResourceDictionary xmlns="https://github.com/avaloniaui"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     <Design.PreviewWith>
         <Border Padding="20">
@@ -9,4 +9,4 @@
     <Style Selector="Button.SocialMediaButton">
 
     </Style>
-</Styles>
+</ResourceDictionary>

+ 1 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/SubViewModels/FileViewModel.cs

@@ -21,6 +21,7 @@ using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.IO;
 using PixiEditor.Parser;
 using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.Views.Dialogs;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main;
 

+ 1 - 5
src/PixiEditor.Avalonia/PixiEditor.Avalonia/ViewModels/Tools/ToolSettings/Settings/SizeSetting.cs

@@ -1,10 +1,6 @@
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using Avalonia.Controls;
+using Avalonia.Controls;
 using Avalonia.Data;
 using Avalonia.Layout;
-using PixiEditor.Views.UserControls;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Settings;
 

+ 60 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/SizeInput.axaml

@@ -0,0 +1,60 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.SizeInput"
+             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:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours"
+             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+             xmlns:views="clr-namespace:PixiEditor.Views"
+             xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+             mc:Ignorable="d" Foreground="White" Focusable="True"
+             d:DesignHeight="30" Name="uc"
+             FlowDirection="LeftToRight">
+    <Border BorderThickness="1" CornerRadius="3.5"
+            x:Name="border"
+            Cursor="IBeam" PointerPressed="Border_MouseLeftButtonDown"
+            PointerWheelChanged="Border_MouseWheel">
+        <Border.Styles>
+            <!--TODO: Handle these colors-->
+            <Style Selector="Border">
+                <Setter Property="Background" Value="{StaticResource DarkerAccentColor}"/>
+                <Setter Property="BorderBrush" Value="{StaticResource AlmostLightModeAccentColor}"/>
+            </Style>
+            <Style Selector="Border:hover">
+                <Setter Property="Background" Value="{StaticResource AccentColor}"/>
+                <Setter Property="BorderBrush" Value="{StaticResource BrighterAccentColor}"/>
+            </Style>
+        </Border.Styles>
+        <Grid>
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition/>
+                <ColumnDefinition Width="2"/>
+                <ColumnDefinition Width="Auto"/>
+            </Grid.ColumnDefinitions>
+            <TextBox IsEnabled="{Binding IsEnabled, ElementName=uc}" HorizontalContentAlignment="Right"
+                     BorderThickness="0" Background="Transparent"
+                     SelectionBrush="{StaticResource SelectionColor}"
+                     Foreground="{Binding Foreground, ElementName=uc}" Focusable="True" CaretBrush="{Binding Foreground, ElementName=uc}"
+                     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>
+                    <behaviors:GlobalShortcutFocusBehavior/>
+                    <behaviors:TextBoxFocusBehavior 
+                        SelectOnMouseClick="{Binding BehaveLikeSmallEmbeddedField, ElementName=uc}" 
+                        ConfirmOnEnter="{Binding BehaveLikeSmallEmbeddedField, ElementName=uc}"
+                        DeselectOnFocusLoss="True"/>
+                </Interaction.Behaviors>
+            </TextBox>
+            <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"
+                       Grid.Column="2" Margin="5,0" VerticalAlignment="Center"
+            />
+        </Grid>
+    </Border>
+</UserControl>

+ 135 - 0
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Input/SizeInput.axaml.cs

@@ -0,0 +1,135 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Hardware.Info;
+using PixiEditor.Models.Enums;
+
+namespace PixiEditor.Views.UserControls;
+
+/// <summary>
+///     Interaction logic for SizeInput.xaml.
+/// </summary>
+internal partial class SizeInput : UserControl
+{
+    public static readonly StyledProperty<int> SizeProperty =
+        AvaloniaProperty.Register<SizeInput, int>(nameof(Size), defaultValue: 1, notifyingSetter: SizePropertyChanged);
+
+    public static readonly StyledProperty<int> MaxSizeProperty =
+        AvaloniaProperty.Register<SizeInput, int>(nameof(MaxSize), defaultValue: int.MaxValue);
+
+    public static readonly StyledProperty<bool> BehaveLikeSmallEmbeddedFieldProperty =
+        AvaloniaProperty.Register<SizeInput, bool>(nameof(BehaveLikeSmallEmbeddedField), defaultValue: true);
+
+    public static readonly StyledProperty<SizeUnit> UnitProperty =
+        AvaloniaProperty.Register<SizeInput, SizeUnit>(nameof(Unit), defaultValue: SizeUnit.Pixel);
+
+    public Action OnScrollAction
+    {
+        get { return GetValue(OnScrollActionProperty); }
+        set { SetValue(OnScrollActionProperty, value); }
+    }
+
+    public static readonly StyledProperty<Action> OnScrollActionProperty =
+        AvaloniaProperty.Register<SizeInput, Action>(nameof(OnScrollAction));
+
+    public SizeInput()
+    {
+        InitializeComponent();
+    }
+
+    private void SizeInput_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
+    {
+        textBox.Focus();
+    }
+
+    public int Size
+    {
+        get => (int)GetValue(SizeProperty);
+        set => SetValue(SizeProperty, value);
+    }
+
+    public int MaxSize
+    {
+        get => (int)GetValue(MaxSizeProperty);
+        set => SetValue(MaxSizeProperty, value);
+    }
+
+    public bool BehaveLikeSmallEmbeddedField
+    {
+        get => (bool)GetValue(BehaveLikeSmallEmbeddedFieldProperty);
+        set => SetValue(BehaveLikeSmallEmbeddedFieldProperty, value);
+    }
+
+    public void FocusAndSelect()
+    {
+        textBox.Focus();
+        textBox.SelectAll();
+    }
+
+    private void Border_MouseLeftButtonDown(object? sender, PointerPressedEventArgs pointerPressedEventArgs)
+    {
+        Point pos = Mouse.GetPosition(textBox);
+        int charIndex = textBox.GetCharacterIndexFromPoint(pos, true);
+        var charRect = textBox.GetRectFromCharacterIndex(charIndex);
+        double middleX = (charRect.Left + charRect.Right) / 2;
+        if (pos.X > middleX)
+            textBox.CaretIndex = charIndex + 1;
+        else
+            textBox.CaretIndex = charIndex;
+        e.Handled = true;
+        if (!textBox.IsFocused)
+            textBox.Focus();
+    }
+
+    public SizeUnit Unit
+    {
+        get => (SizeUnit)GetValue(UnitProperty);
+        set => SetValue(UnitProperty, value);
+    }
+
+    private static void InputSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        int newValue = (int)e.NewValue;
+        int maxSize = (int)d.GetValue(MaxSizeProperty);
+
+        if (newValue > maxSize)
+        {
+            d.SetValue(SizeProperty, maxSize);
+
+            return;
+        }
+        else if (newValue <= 0)
+        {
+            d.SetValue(SizeProperty, 1);
+
+            return;
+        }
+    }
+
+    private void Border_MouseWheel(object? sender, PointerWheelEventArgs pointerWheelEventArgs)
+    {
+        int step = e.Delta / 100;
+
+        if (Keyboard.IsKeyDown(Key.LeftShift))
+        {
+            Size += step * 2;
+        }
+        else if (Keyboard.IsKeyDown(Key.LeftCtrl))
+        {
+            if (step < 0)
+            {
+                Size /= 2;
+            }
+            else
+            {
+                Size *= 2;
+            }
+        }
+        else
+        {
+            Size += step;
+        }
+
+        OnScrollAction?.Invoke();
+    }
+}

+ 10 - 9
src/PixiEditor.Avalonia/PixiEditor.Avalonia/Views/Windows/HelloTherePopup.axaml

@@ -58,7 +58,7 @@
                                 <StackPanel.Background>
                                     <VisualBrush>
                                         <VisualBrush.Visual>
-                                            <Ellipse Fill="{StaticResource MainColor}" Width="20" Height="20"/>
+                                            <Ellipse Fill="{StaticResource ThemeBackgroundBrush}" Width="20" Height="20"/>
                                         </VisualBrush.Visual>
                                     </VisualBrush>
                                 </StackPanel.Background>
@@ -69,7 +69,8 @@
                                 </Image>
                                 <ContentPresenter Focusable="False"/>
                             </StackPanel>
-                            <ControlTemplate.Triggers>
+
+                            <!--<ControlTemplate.Triggers>
                                 <Trigger Property="IsChecked" Value="True">
                                     <Setter TargetName="checkboxImage" Property="RenderTransform">
                                         <Setter.Value>
@@ -77,7 +78,7 @@
                                         </Setter.Value>
                                     </Setter>
                                 </Trigger>
-                            </ControlTemplate.Triggers>
+                            </ControlTemplate.Triggers>-->
                         </ControlTemplate>
                     </CheckBox.Template>
                 </CheckBox>
@@ -125,17 +126,17 @@
                                                 x:Name="fileButton">
                                             <Grid Width="100" Height="100">
                                                 <Image Source="{Binding PreviewBitmap}" x:Name="image" Margin="20">
-                                                    <RenderOptions.BitmapScalingMode>
+                                                    <RenderOptions.BitmapInterpolationMode>
                                                         <MultiBinding Converter="{converters:WidthToBitmapScalingModeConverter}">
                                                             <Binding Path="PreviewBitmap.PixelWidth"/>
                                                             <Binding ElementName="image" Path="ActualWidth"/>
                                                         </MultiBinding>
-                                                    </RenderOptions.BitmapScalingMode>
+                                                    </RenderOptions.BitmapInterpolationMode>
                                                 </Image>
                                                 <Border Grid.Row="1" Height="8" Width="8" x:Name="extensionBorder" Margin="5"
                                                         Background="{Binding FileExtension, Converter={converters:FileExtensionToColorConverter}}" 
                                                         VerticalAlignment="Bottom" HorizontalAlignment="Right">
-                                                    <Border.Style>
+                                                    <!--<Border.Style>
                                                         <Style TargetType="Border">
                                                             <Style.Triggers>
                                                                 <Trigger Property="IsMouseOver" Value="False">
@@ -188,9 +189,9 @@
                                                                 </DataTrigger>
                                                             </Style.Triggers>
                                                         </Style>
-                                                    </Border.Style>
+                                                    </Border.Style>-->
                                                     <Grid HorizontalAlignment="Center" Margin="0,10,0,0" Opacity="0">
-                                                        <Grid.Style>
+                                                        <!--<Grid.Style>
                                                             <Style TargetType="Grid">
                                                                 <Style.Triggers>
                                                                     <DataTrigger Binding="{Binding IsMouseOver, ElementName=fileButton}" Value="True">
@@ -211,7 +212,7 @@
                                                                     </DataTrigger>
                                                                 </Style.Triggers>
                                                             </Style>
-                                                        </Grid.Style>
+                                                        </Grid.Style>-->
                                                         <TextBlock x:Name="extension" VerticalAlignment="Top" Text="{Binding FileExtension}" FontSize="15" TextAlignment="Center"/>
                                                         <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Center">
                                                             <StackPanel.Resources>