Browse Source

Added better string editing

Krzysztof Krysiński 5 months ago
parent
commit
6dd39eaf3f

+ 1 - 1
src/Drawie

@@ -1 +1 @@
-Subproject commit d8709f209277059a0b514a2294d8236411d597b9
+Subproject commit 92190651911271f52a03fed63eb7ad2679b77430

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Effects/OutlineNode.cs

@@ -10,7 +10,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Effects;
 
 [NodeInfo("Outline")]
-public class OutlineNode : RenderNode, IRenderInput, ICustomShaderNode
+public class OutlineNode : RenderNode, IRenderInput
 {
     public RenderInputProperty Background { get; }
     public InputProperty<OutlineType> Type { get; }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/ShaderNode.cs

@@ -10,7 +10,7 @@ using PixiEditor.ChangeableDocument.Rendering;
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 
 [NodeInfo("Shader")]
-public class ShaderNode : RenderNode, IRenderInput
+public class ShaderNode : RenderNode, IRenderInput, ICustomShaderNode
 {
     public RenderInputProperty Background { get; }
     public InputProperty<string> ShaderCode { get; }

+ 7 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -859,5 +859,11 @@
   "THICKNESS": "Thickness",
   "TYPE": "Type",
   "EFFECTS": "Effects",
-  "OUTLINE_NODE": "Outline"
+  "OUTLINE_NODE": "Outline",
+  "SHADER_CODE": "Shader Code",
+  "SHADER_NODE": "Shader",
+  "FAILED_TO_OPEN_EDITABLE_STRING_TITLE": "Failed to open file",
+  "FAILED_TO_OPEN_EDITABLE_STRING_MESSAGE": "Failed to edit this string in external editor. Reason: {0}",
+  "STRING_EDIT_IN_DEFAULT_APP": "Edit in default app",
+  "STRING_OPEN_IN_FOLDER": "Open in folder"
 }

+ 24 - 2
src/PixiEditor/ViewModels/Document/Nodes/ShaderNodeViewModel.cs

@@ -1,7 +1,29 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using System.Collections.Specialized;
+using Drawie.Backend.Core.Bridge;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ViewModels.Nodes;
+using PixiEditor.ViewModels.Nodes.Properties;
 
 namespace PixiEditor.ViewModels.Document.Nodes;
 
 [NodeViewModel("SHADER_NODE", "EFFECTS", "\uE80B")]
-internal class ShaderNodeViewModel : NodeViewModel<ShaderNode>;
+internal class ShaderNodeViewModel : NodeViewModel<ShaderNode>
+{
+    public ShaderNodeViewModel()
+    {
+        Inputs.CollectionChanged += InputsOnCollectionChanged;
+    }
+
+    private void InputsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+    {
+        if(e.NewItems == null) return;
+
+        foreach (var newItem in e.NewItems)
+        {
+            if (newItem is StringPropertyViewModel stringPropertyViewModel)
+            {
+                stringPropertyViewModel.Kind = DrawingBackendApi.Current.ShaderImplementation.ShaderLanguageExtension;
+            }
+        }
+    }
+}

+ 96 - 2
src/PixiEditor/ViewModels/Nodes/Properties/StringPropertyViewModel.cs

@@ -1,20 +1,39 @@
 using System.ComponentModel;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+using CommunityToolkit.Mvvm.Input;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Dialogs;
+using PixiEditor.Models.Handlers;
+using PixiEditor.Models.IO;
+using PixiEditor.OperatingSystem;
 
 namespace PixiEditor.ViewModels.Nodes.Properties;
 
 internal class StringPropertyViewModel : NodePropertyViewModel<string>
 {
+    private string fileWatcherPath = string.Empty;
+    private FileSystemWatcher fileWatcher;
+
+    public RelayCommand OpenInDefaultAppCommand { get; }
+    public RelayCommand OpenInFolderCommand { get; }
+
     public string StringValue
     {
         get => Value;
         set => Value = value;
     }
-    
+
+    public string Kind = "txt";
+
     public StringPropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
     {
         PropertyChanged += StringPropertyViewModel_PropertyChanged;
+        OpenInDefaultAppCommand = new RelayCommand(OpenInDefaultApp);
+        OpenInFolderCommand = new RelayCommand(OpenInFolder);
     }
-    
+
     private void StringPropertyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
     {
         if (e.PropertyName == nameof(Value))
@@ -22,4 +41,79 @@ internal class StringPropertyViewModel : NodePropertyViewModel<string>
             OnPropertyChanged(nameof(StringValue));
         }
     }
+
+    private void OpenInDefaultApp()
+    {
+        try
+        {
+            if (!string.IsNullOrEmpty(fileWatcherPath) && File.Exists(fileWatcherPath))
+            {
+                OpenInDefaultApp(fileWatcherPath);
+                return;
+            }
+
+            fileWatcherPath = CreateTempFile();
+            CreateFileWatcher(fileWatcherPath);
+            OpenInDefaultApp(fileWatcherPath);
+        }
+        catch (Exception ex)
+        {
+            NoticeDialog.Show(new LocalizedString("FAILED_TO_OPEN_EDITABLE_STRING_MESSAGE", ex.Message),
+                "FAILED_TO_OPEN_EDITABLE_STRING_TITLE");
+            CrashHelper.SendExceptionInfo(ex);
+        }
+    }
+
+    private void OpenInFolder()
+    {
+        if (!string.IsNullOrEmpty(fileWatcherPath) && File.Exists(fileWatcherPath))
+        {
+            IOperatingSystem.Current.OpenFolder(fileWatcherPath);
+            return;
+        }
+
+        fileWatcherPath = CreateTempFile();
+        CreateFileWatcher(fileWatcherPath);
+        IOperatingSystem.Current.OpenFolder(fileWatcherPath);
+    }
+
+    private string CreateTempFile()
+    {
+        string extension = $".{Kind}";
+
+        string dirPath = Path.Combine(Paths.TempFilesPath, "NodeProps");
+        if (!Directory.Exists(dirPath))
+        {
+            Directory.CreateDirectory(dirPath);
+        }
+
+        string filePath = Path.Combine(dirPath, Guid.NewGuid().ToString("N") + extension);
+        File.WriteAllText(filePath, StringValue);
+
+        return filePath;
+    }
+
+    private void CreateFileWatcher(string filePath)
+    {
+        fileWatcher?.Dispose();
+        fileWatcher = new FileSystemWatcher();
+        fileWatcher.Path = Path.GetDirectoryName(filePath);
+        fileWatcher.Filter = Path.GetFileName(filePath);
+        fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
+
+        fileWatcher.Changed += (sender, args) =>
+        {
+            using FileStream stream = new(args.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+            using StreamReader reader = new(stream);
+            string text = reader.ReadToEnd();
+            Dispatcher.UIThread.Post(() => StringValue = text);
+        };
+
+        fileWatcher.EnableRaisingEvents = true;
+    }
+
+    private void OpenInDefaultApp(string path)
+    {
+        IOperatingSystem.Current.OpenUri(path);
+    }
 }

+ 56 - 9
src/PixiEditor/Views/Nodes/Properties/StringPropertyView.axaml

@@ -11,13 +11,60 @@
                              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                              x:DataType="properties1:StringPropertyViewModel"
                              x:Class="PixiEditor.Views.Nodes.Properties.StringPropertyView">
-    <DockPanel LastChildFill="True"
-        HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
-        <TextBox AcceptsReturn="True" Text="{CompiledBinding StringValue, Mode=TwoWay}" IsVisible="{Binding ShowInputField}">
-            <Interaction.Behaviors>
-                <behaviours:GlobalShortcutFocusBehavior />
-            </Interaction.Behaviors>
-        </TextBox>
-    </DockPanel>
+    <Grid>
+        <DockPanel LastChildFill="True"
+                   HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
+            <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
+            <TextBox MaxWidth="110"
+                     MaxLines="1"
+                     Name="smallTextBox"
+                     Text="{CompiledBinding StringValue, Mode=TwoWay}"
+                     IsVisible="{Binding ShowInputField}">
+                <TextBox.InnerRightContent>
+                    <ToggleButton Name="bigModeToggle" DockPanel.Dock="Right" FontSize="20" Classes="pixi-icon"
+                                  Content="{DynamicResource icon-link}" />
+                </TextBox.InnerRightContent>
+                <Interaction.Behaviors>
+                    <behaviours:GlobalShortcutFocusBehavior />
+                </Interaction.Behaviors>
+            </TextBox>
+        </DockPanel>
+        <Popup IsOpen="{Binding ElementName=bigModeToggle, Path=IsChecked, Mode=TwoWay}"
+               Placement="Bottom"
+               IsLightDismissEnabled="True"
+               Opened="Popup_OnOpened"
+               PlacementTarget="{Binding ElementName=bigModeToggle}">
+            <DockPanel LastChildFill="True">
+                <Border CornerRadius="5 5 0 0" DockPanel.Dock="Top"
+                        Background="{DynamicResource ThemeBackgroundBrush1}">
+                    <StackPanel Orientation="Horizontal">
+                        <Button Margin="5" Classes="pixi-icon"
+                                Name="openInDefaultAppButton"
+                                Command="{Binding OpenInDefaultAppCommand}"
+                                ui:Translator.TooltipKey="STRING_EDIT_IN_DEFAULT_APP"
+                                Content="{DynamicResource icon-link}" FontSize="20" />
+                        <Button Margin="5" Classes="pixi-icon"
+                                Name="openInFolderButton"
+                                ui:Translator.TooltipKey="STRING_OPEN_IN_FOLDER"
+                                Command="{Binding OpenInFolderCommand}"
+                                Content="{DynamicResource icon-folder}" FontSize="20" />
+                    </StackPanel>
+                </Border>
+                <TextBox
+                    CornerRadius="0 0 5 5"
+                    Name="bigTextBox"
+                    Width="500"
+                    Height="600"
+                    AcceptsReturn="True"
+                    AcceptsTab="True"
+                    PointerWheelChanged="InputElement_OnPointerWheelChanged"
+                    Text="{Binding StringValue, Mode=TwoWay}"
+                    IsVisible="{Binding ElementName=bigModeToggle, Path=IsChecked}">
+                    <Interaction.Behaviors>
+                        <behaviours:GlobalShortcutFocusBehavior />
+                    </Interaction.Behaviors>
+                </TextBox>
+            </DockPanel>
+        </Popup>
+    </Grid>
 </properties:NodePropertyView>

+ 39 - 2
src/PixiEditor/Views/Nodes/Properties/StringPropertyView.axaml.cs

@@ -1,14 +1,51 @@
-using Avalonia;
+using System.Windows.Input;
+using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Interactivity;
 using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+using PixiEditor.Extensions.Common.Localization;
+using PixiEditor.Models.Dialogs;
+using PixiEditor.Models.Handlers;
+using PixiEditor.Models.IO;
+using PixiEditor.OperatingSystem;
+using PixiEditor.ViewModels.Nodes.Properties;
 
 namespace PixiEditor.Views.Nodes.Properties;
 
 public partial class StringPropertyView : NodePropertyView
 {
+    public static readonly StyledProperty<ICommand> OpenInDefaultAppCommandProperty = AvaloniaProperty.Register<StringPropertyView, ICommand>(
+        nameof(OpenInDefaultAppCommand));
+
+    public ICommand OpenInDefaultAppCommand
+    {
+        get => GetValue(OpenInDefaultAppCommandProperty);
+        set => SetValue(OpenInDefaultAppCommandProperty, value);
+    }
     public StringPropertyView()
     {
         InitializeComponent();
     }
-}
 
+    protected override void OnLoaded(RoutedEventArgs e)
+    {
+        base.OnLoaded(e);
+        ScrollViewer scroll = smallTextBox.FindDescendantOfType<ScrollViewer>();
+        scroll.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
+        scroll.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
+    }
+
+    private void InputElement_OnPointerWheelChanged(object? sender, PointerWheelEventArgs e)
+    {
+        e.Handled = true;
+    }
+
+    private void Popup_OnOpened(object? sender, EventArgs e)
+    {
+        Dispatcher.UIThread.Post(() => bigTextBox.Focus());
+    }
+}