瀏覽代碼

[Avalonia] EditableTitlebar refactor and bug fixes

Ruben 1 年之前
父節點
當前提交
896bde0a3d

+ 2 - 16
src/PicView.Avalonia.MacOS/Views/MacOSTitlebar.axaml

@@ -5,9 +5,9 @@
     x:DataType="viewModels:MainViewModel"
     x:DataType="viewModels:MainViewModel"
     x:Name="Titlebar"
     x:Name="Titlebar"
     xmlns="https://github.com/avaloniaui"
     xmlns="https://github.com/avaloniaui"
-    xmlns:customControls="clr-namespace:PicView.Avalonia.CustomControls;assembly=PicView.Avalonia"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:uc="clr-namespace:PicView.Avalonia.Views.UC;assembly=PicView.Avalonia"
     xmlns:viewModels="clr-namespace:PicView.Avalonia.ViewModels;assembly=PicView.Avalonia"
     xmlns:viewModels="clr-namespace:PicView.Avalonia.ViewModels;assembly=PicView.Avalonia"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     <Design.DataContext>
     <Design.DataContext>
@@ -71,20 +71,6 @@
             </Image>
             </Image>
         </Button>
         </Button>
 
 
-        <customControls:EditableTitlebar
-            Background="Transparent"
-            FontFamily="avares://PicView.Avalonia/Assets/Fonts/Roboto-Medium.ttf#Roboto"
-            FontSize="13"
-            FontWeight="Medium"
-            Foreground="{StaticResource MainTextColor}"
-            Height="{CompiledBinding TitlebarHeight}"
-            IsTabStop="False"
-            MaxWidth="{CompiledBinding TitleMaxWidth}"
-            Name="EditableTitlebar"
-            Padding="0,7,0,5"
-            Text="{CompiledBinding Title}"
-            TextAlignment="Center"
-            ToolTip.Tip="{CompiledBinding TitleTooltip}"
-            VerticalAlignment="Center" />
+        <uc:EditableTitlebar Background="Transparent" Foreground="{StaticResource MainTextColor}" />
     </DockPanel>
     </DockPanel>
 </UserControl>
 </UserControl>

+ 2 - 18
src/PicView.Avalonia.Win32/Views/WinTitleBar.axaml

@@ -12,6 +12,7 @@
     xmlns:customControls="clr-namespace:PicView.Avalonia.CustomControls;assembly=PicView.Avalonia"
     xmlns:customControls="clr-namespace:PicView.Avalonia.CustomControls;assembly=PicView.Avalonia"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:uc="clr-namespace:PicView.Avalonia.Views.UC;assembly=PicView.Avalonia"
     xmlns:vm="clr-namespace:PicView.Avalonia.ViewModels;assembly=PicView.Avalonia"
     xmlns:vm="clr-namespace:PicView.Avalonia.ViewModels;assembly=PicView.Avalonia"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 
 
@@ -159,24 +160,7 @@
                         Stretch="Fill" />
                         Stretch="Fill" />
                 </Viewbox>
                 </Viewbox>
             </Button>
             </Button>
-            <customControls:EditableTitlebar
-                Background="{StaticResource SecondaryBackgroundColor}"
-                FontFamily="avares://PicView.Avalonia/Assets/Fonts/Roboto-Medium.ttf#Roboto"
-                FontSize="13"
-                FontWeight="Medium"
-                Foreground="{StaticResource MainTextColor}"
-                Height="{CompiledBinding TitlebarHeight,
-                                         Mode=OneWay}"
-                IsTabStop="False"
-                MaxWidth="{CompiledBinding TitleMaxWidth,
-                                           Mode=OneWay}"
-                Name="EditableTitlebar"
-                Text="{CompiledBinding Title,
-                                       Mode=OneWay}"
-                TextAlignment="Center"
-                ToolTip.Tip="{CompiledBinding TitleTooltip,
-                                              Mode=OneWay}"
-                VerticalAlignment="Center" />
+            <uc:EditableTitlebar Background="{StaticResource SecondaryBackgroundColor}" x:Name="EditableTitlebar" />
         </DockPanel>
         </DockPanel>
     </Border>
     </Border>
 </UserControl>
 </UserControl>

+ 10 - 0
src/PicView.Avalonia.Win32/Views/WinTitleBar.axaml.cs

@@ -1,5 +1,6 @@
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Input;
+using PicView.Avalonia.ViewModels;
 
 
 namespace PicView.Avalonia.Win32.Views;
 namespace PicView.Avalonia.Win32.Views;
 
 
@@ -15,6 +16,15 @@ public partial class WinTitleBar : UserControl
     {
     {
         if (VisualRoot is null) { return; }
         if (VisualRoot is null) { return; }
 
 
+        if (DataContext is not MainViewModel vm)
+        {
+            return;
+        }
+        if (vm.IsEditableTitlebarOpen)
+        {
+            return;
+        }
+
         var hostWindow = (Window)VisualRoot;
         var hostWindow = (Window)VisualRoot;
         hostWindow?.BeginMoveDrag(e);
         hostWindow?.BeginMoveDrag(e);
     }
     }

+ 0 - 82
src/PicView.Avalonia/DarkTheme/Controls/EditableTitlebar.axaml

@@ -1,82 +0,0 @@
-<ResourceDictionary
-    x:ClassModifier="internal"
-    xmlns="https://github.com/avaloniaui"
-    xmlns:customControls="clr-namespace:PicView.Avalonia.CustomControls"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-
-    <ControlTheme TargetType="customControls:EditableTitlebar" x:Key="{x:Type customControls:EditableTitlebar}">
-        <Setter Property="CaretBrush" Value="{StaticResource MainTextColor}" />
-        <Setter Property="SelectionBrush" Value="{DynamicResource AccentColor}" />
-        <Setter Property="SelectionForegroundBrush" Value="{StaticResource MainTextColor}" />
-        <Setter Property="ScrollViewer.IsScrollChainingEnabled" Value="True" />
-        <Setter Property="Template">
-            <ControlTemplate>
-                <Border
-                    Background="{TemplateBinding Background}"
-                    BorderBrush="{TemplateBinding BorderBrush}"
-                    BorderThickness="{TemplateBinding BorderThickness}"
-                    CornerRadius="{TemplateBinding CornerRadius}"
-                    Name="border">
-                    <DockPanel
-                        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
-                        Margin="{TemplateBinding Padding}"
-                        VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
-
-                        <DataValidationErrors>
-                            <Panel>
-                                <ContentPresenter Content="{TemplateBinding InnerLeftContent}" />
-                                <ScrollViewer
-                                    AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
-                                    BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
-                                    HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
-                                    IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
-                                    Name="PART_ScrollViewer"
-                                    VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
-                                    <Border
-                                        Background="Transparent"
-                                        BorderBrush="{DynamicResource AccentColor}"
-                                        BorderThickness="1.2"
-                                        CornerRadius="1"
-                                        IsVisible="False"
-                                        Name="PART_Border">
-                                        <TextPresenter
-                                            CaretBrush="{TemplateBinding CaretBrush}"
-                                            CaretIndex="{TemplateBinding CaretIndex}"
-                                            LetterSpacing="{TemplateBinding LetterSpacing}"
-                                            LineHeight="{TemplateBinding LineHeight}"
-                                            Name="PART_TextPresenter"
-                                            SelectionBrush="{TemplateBinding SelectionBrush}"
-                                            SelectionEnd="{TemplateBinding SelectionEnd}"
-                                            SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
-                                            SelectionStart="{TemplateBinding SelectionStart}"
-                                            Text="{TemplateBinding Text,
-                                                                   Mode=OneWay}"
-                                            TextAlignment="{TemplateBinding TextAlignment}"
-                                            TextWrapping="{TemplateBinding TextWrapping}"
-                                            VerticalAlignment="Center" />
-                                    </Border>
-                                </ScrollViewer>
-                                <TextBlock
-                                    LetterSpacing="{TemplateBinding LetterSpacing}"
-                                    LineHeight="{TemplateBinding LineHeight}"
-                                    Name="PART_TextBlock"
-                                    Text="{TemplateBinding Text,
-                                                           Mode=OneWay}"
-                                    TextAlignment="{TemplateBinding TextAlignment}"
-                                    TextTrimming="CharacterEllipsis"
-                                    VerticalAlignment="Center" />
-                            </Panel>
-                        </DataValidationErrors>
-                    </DockPanel>
-                </Border>
-            </ControlTemplate>
-        </Setter>
-
-        <Style Selector="^:error /template/ Border#border">
-            <Setter Property="BorderBrush" Value="{DynamicResource ErrorBrush}" />
-        </Style>
-        <Style Selector="^:disabled /template/ Border#border">
-            <Setter Property="Background" Value="{StaticResource BackgroundAlpha}" />
-        </Style>
-    </ControlTheme>
-</ResourceDictionary>

+ 0 - 1
src/PicView.Avalonia/DarkTheme/Main.axaml

@@ -51,7 +51,6 @@
                 <ResourceInclude Source="/DarkTheme/Controls/Button.axaml" />
                 <ResourceInclude Source="/DarkTheme/Controls/Button.axaml" />
                 <ResourceInclude Source="/DarkTheme/Controls/ComboBox.axaml" />
                 <ResourceInclude Source="/DarkTheme/Controls/ComboBox.axaml" />
                 <ResourceInclude Source="/DarkTheme/Controls/ComboBoxItem.axaml" />
                 <ResourceInclude Source="/DarkTheme/Controls/ComboBoxItem.axaml" />
-                <ResourceInclude Source="/DarkTheme/Controls/EditableTitlebar.axaml" />
                 <ResourceInclude Source="/DarkTheme/Controls/FlyoutPresenter.axaml" />
                 <ResourceInclude Source="/DarkTheme/Controls/FlyoutPresenter.axaml" />
                 <ResourceInclude Source="/DarkTheme/Controls/ListBox.axaml" />
                 <ResourceInclude Source="/DarkTheme/Controls/ListBox.axaml" />
                 <ResourceInclude Source="/DarkTheme/Controls/ListBoxItem.axaml" />
                 <ResourceInclude Source="/DarkTheme/Controls/ListBoxItem.axaml" />

+ 1 - 2
src/PicView.Avalonia/UI/UIHelper.cs

@@ -2,13 +2,12 @@
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Templates;
 using Avalonia.Layout;
 using Avalonia.Layout;
 using Avalonia.Threading;
 using Avalonia.Threading;
-using PicView.Avalonia.CustomControls;
 using PicView.Avalonia.Navigation;
 using PicView.Avalonia.Navigation;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.Views;
 using PicView.Avalonia.Views;
+using PicView.Avalonia.Views.UC;
 using PicView.Core.Config;
 using PicView.Core.Config;
 using PicView.Core.Localization;
 using PicView.Core.Localization;
 
 

+ 8 - 0
src/PicView.Avalonia/ViewModels/MainViewModel.cs

@@ -665,6 +665,14 @@ public class MainViewModel : ViewModelBase
         set => this.RaiseAndSetIfChanged(ref _isOpeningInSameWindow, value);
         set => this.RaiseAndSetIfChanged(ref _isOpeningInSameWindow, value);
     }
     }
 
 
+    private bool _isEditableTitlebarOpen;
+    
+    public bool IsEditableTitlebarOpen
+    {
+        get => _isEditableTitlebarOpen;
+        set => this.RaiseAndSetIfChanged(ref _isEditableTitlebarOpen, value);
+    }
+
     #endregion Booleans
     #endregion Booleans
 
 
     public double WindowMinSize
     public double WindowMinSize

+ 6 - 2
src/PicView.Avalonia/Views/MainView.axaml.cs

@@ -29,9 +29,13 @@ public partial class MainView : UserControl
     
     
     private void CloseTitlebarIfOpen(object? sender, EventArgs e)
     private void CloseTitlebarIfOpen(object? sender, EventArgs e)
     {
     {
-        if (UIHelper.GetEditableTitlebar.IsOpen)
+        if (DataContext is not MainViewModel vm)
+        {
+            return;
+        }
+        if (vm.IsEditableTitlebarOpen)
         {
         {
-            UIHelper.GetEditableTitlebar.CloseTitlebar();
+            vm.IsEditableTitlebarOpen = false;
         }
         }
     }
     }
 
 

+ 50 - 0
src/PicView.Avalonia/Views/UC/EditableTitlebar.axaml

@@ -0,0 +1,50 @@
+<UserControl
+    Height="{CompiledBinding TitlebarHeight,
+                             Mode=OneWay}"
+    Width="{CompiledBinding TitleMaxWidth,
+                            Mode=OneWay}"
+    d:DesignHeight="450"
+    d:DesignWidth="800"
+    mc:Ignorable="d"
+    x:Class="PicView.Avalonia.Views.UC.EditableTitlebar"
+    x:DataType="viewModels:MainViewModel"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:viewModels="clr-namespace:PicView.Avalonia.ViewModels"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <Panel>
+        <TextBlock
+            FontFamily="avares://PicView.Avalonia/Assets/Fonts/Roboto-Medium.ttf#Roboto"
+            FontSize="13"
+            FontWeight="Medium"
+            Height="{CompiledBinding TitlebarHeight,
+                                     Mode=OneWay}"
+            IsTabStop="False"
+            IsVisible="{CompiledBinding !IsEditableTitlebarOpen}"
+            Padding="0,7,0,5"
+            Text="{CompiledBinding Title,
+                                   Mode=OneWay}"
+            TextAlignment="Center"
+            TextTrimming="CharacterEllipsis"
+            ToolTip.Tip="{Binding TitleTooltip, Mode=OneWay}"
+            VerticalAlignment="Center"
+            x:Name="TextBlock" />
+        <Border
+            BorderBrush="{DynamicResource AccentColor}"
+            BorderThickness="1"
+            IsVisible="{CompiledBinding IsEditableTitlebarOpen}">
+            <TextBox
+                FontFamily="avares://PicView.Avalonia/Assets/Fonts/Roboto-Medium.ttf#Roboto"
+                FontSize="13"
+                FontWeight="Medium"
+                Height="{CompiledBinding TitlebarHeight,
+                                         Mode=OneWay}"
+                IsTabStop="False"
+                Padding="0,7,0,5"
+                TextAlignment="Center"
+                VerticalAlignment="Center"
+                x:Name="TextBox" />
+        </Border>
+    </Panel>
+</UserControl>

+ 236 - 260
src/PicView.Avalonia/CustomControls/EditableTitlebar.cs → src/PicView.Avalonia/Views/UC/EditableTitlebar.axaml.cs

@@ -1,260 +1,236 @@
-using Avalonia.Controls;
-using Avalonia.Controls.Metadata;
-using Avalonia.Controls.Presenters;
-using Avalonia.Controls.Primitives;
-using Avalonia.Input;
-using Avalonia.Interactivity;
-using Avalonia.Threading;
-using PicView.Avalonia.Keybindings;
-using PicView.Avalonia.Navigation;
-using PicView.Avalonia.UI;
-using PicView.Avalonia.ViewModels;
-using PicView.Core.FileHandling;
-using PicView.Core.ImageDecoding;
-
-namespace PicView.Avalonia.CustomControls;
-
-[TemplatePart("PART_TextBlock", typeof(TextBlock))]
-[TemplatePart("PART_TextPresenter", typeof(TextPresenter))]
-[TemplatePart("PART_Border", typeof(Border))]
-public class EditableTitlebar : TextBox
-{
-    #region Properties
-    protected override Type StyleKeyOverride => typeof(EditableTitlebar);
-
-    public bool IsRenaming
-    {
-        get
-        {
-            if (DataContext is not MainViewModel vm)
-            {
-                return false;
-            }
-            return vm.ImageIterator?.IsRenamingInProgress ?? false;
-        }
-        private set
-        {
-            if (DataContext is not MainViewModel vm)
-            {
-                return;
-            }
-            vm.ImageIterator.IsRenamingInProgress = value;
-        }
-    }
-    
-    public bool IsOpen { get; private set; }
-    
-    private TextBlock? _textBlock;
-
-    private Border? _border;
-    
-    #endregion
-    
-    #region Constructor, overrides and behavior
-
-    public EditableTitlebar()
-    {
-        LostFocus += OnLostFocus;
-    }
-
-    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
-    {
-        base.OnApplyTemplate(e);
-        _textBlock = e.NameScope.Find<TextBlock>("PART_TextBlock");
-        _border = e.NameScope.Find<Border>("PART_Border");
-    }
-
-    protected override void OnPointerPressed(PointerPressedEventArgs e)
-    {
-        if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
-        {
-            if (IsRenaming || IsOpen)
-            {
-                return;
-            }
-
-            IsOpen = true;
-            SelectFileName();
-            return;
-        }
-
-        if (IsRenaming || VisualRoot is null)
-        {
-            return;
-        }
-        
-        var hostWindow = (Window)VisualRoot;
-        if (e.ClickCount == 2 && e.GetCurrentPoint(hostWindow).Properties.IsLeftButtonPressed && !IsOpen)
-        {
-            _ = WindowHelper.MaximizeRestore();
-            return;
-        }
-        hostWindow.BeginMoveDrag(e);
-    }
-
-    private void OnLostFocus(object? sender, RoutedEventArgs e)
-    {
-        CloseTitlebar();
-    }
-    
-    public void CloseTitlebar()
-    {
-        ClearSelection();
-        if (DataContext is not MainViewModel vm)
-        {
-            return;
-        }
-        SetTitleHelper.RefreshTitle(vm);
-        _textBlock.IsVisible = true;
-        _border.IsVisible = false;
-        Cursor = new Cursor(StandardCursorType.Arrow);
-        MainKeyboardShortcuts.IsKeysEnabled = true;
-        IsOpen = false;
-        Text = vm.Title;
-    }
-    
-    #endregion
-
-    #region Rename
-    
-    protected override void OnKeyDown(KeyEventArgs e)
-    {
-        MainKeyboardShortcuts.IsKeysEnabled = false;
-        base.OnKeyDown(e);
-    }
-    
-    protected override void OnKeyUp(KeyEventArgs e)
-    {
-        base.OnKeyUp(e);
-        if (e.Key is Key.Enter or Key.Return)
-        {
-            _ = HandleRename();
-            MainKeyboardShortcuts.IsKeysEnabled = true;
-        }
-        else if (e.Key == Key.Escape)
-        {
-            UIHelper.GetMainView.Focus();
-            MainKeyboardShortcuts.IsKeysEnabled = true;
-        }
-    }
-
-    private async Task HandleRename()
-    {
-        if (DataContext is not MainViewModel vm)
-        {
-            return;
-        }
-
-        if (!NavigationHelper.CanNavigate(vm))
-        {
-            return;
-        }
-        
-        if (vm.FileInfo is null)
-        {
-            return;
-        }
-        vm.IsLoading = true;
-        IsRenaming = true;
-        var oldPath = vm.FileInfo.FullName;
-        var newPath = Path.Combine(vm.FileInfo.DirectoryName, Text);
-        
-        // Check if the file is being moved to a different directory
-        if (Path.GetDirectoryName(oldPath) != Path.GetDirectoryName(newPath))
-        {
-            await Renamed().ConfigureAwait(false);
-            return;
-        }
-        
-        // Handle renaming with different extensions
-        if (Path.GetExtension(newPath) != Path.GetExtension(oldPath))
-        {
-            var saved = await SaveImageFileHelper.SaveImageAsync(stream: null, path: oldPath, destination: newPath, width:null, height: null, quality: null, ext: Path.GetExtension(newPath)).ConfigureAwait(false);
-            while (FileHelper.IsFileInUse(oldPath))
-            {
-                await Task.Delay(50); // Fixes "this action can't be completed because the file is open"
-            }
-
-            if (saved)
-            {
-                // Navigate to newly saved file
-                await NavigationHelper.LoadPicFromFile(newPath, vm).ConfigureAwait(false);
-                
-                // Delete old file
-                var deleteMsg = FileDeletionHelper.DeleteFileWithErrorMsg(oldPath, false);
-                if (!string.IsNullOrWhiteSpace(deleteMsg))
-                {
-                    // Show error message to user
-                    await TooltipHelper.ShowTooltipMessageAsync(deleteMsg);
-                    vm.IsLoading = false;
-                    return;
-                }
-            }
-            await End();
-            return;
-        }
-        var renamed = await Renamed().ConfigureAwait(false);
-        if (!renamed)
-        {
-            IsRenaming = false;
-            return;
-        }
-        await End();
-        return;
-
-        async Task<bool> Renamed()
-        {
-            return await Task.FromResult(FileHelper.RenameFile(oldPath, newPath));
-        }
-
-        async Task End()
-        {
-            SetTitleHelper.SetTitle(vm);
-            vm.IsLoading = false;
-            IsRenaming = false;
-            await Dispatcher.UIThread.InvokeAsync(() =>
-            {
-                ClearSelection();
-                SetTitleHelper.RefreshTitle(vm);
-                _textBlock.IsVisible = true;
-                _border.IsVisible = false;
-                Cursor = new Cursor(StandardCursorType.Arrow);
-                MainKeyboardShortcuts.IsKeysEnabled = true;
-                UIHelper.GetMainView.Focus();
-            });
-        }
-    }
-    
-    public void SelectFileName()
-    {
-        if (DataContext is not MainViewModel vm)
-        {
-            return;
-        }
-
-        if (!NavigationHelper.CanNavigate(vm))
-        {
-            return;
-        }
-        
-        if (vm.FileInfo is null)
-        {
-            return;
-        }
-        if (!string.IsNullOrEmpty(Text))
-        {
-            Text = vm.FileInfo.Name;
-        }
-        var filename = vm.FileInfo.Name;
-        var start = Text.Length - filename.Length;
-        var end = Path.GetFileNameWithoutExtension(filename).Length;
-        SelectionStart = start;
-        SelectionEnd = end;
-        _textBlock.IsVisible = false;
-        _border.IsVisible = true;
-        Cursor = new Cursor(StandardCursorType.Ibeam);
-        Focus();
-    }
-
-    #endregion
-}
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Threading;
+using PicView.Avalonia.Keybindings;
+using PicView.Avalonia.Navigation;
+using PicView.Avalonia.UI;
+using PicView.Avalonia.ViewModels;
+using PicView.Core.FileHandling;
+using PicView.Core.ImageDecoding;
+
+namespace PicView.Avalonia.Views.UC;
+
+public partial class EditableTitlebar : UserControl
+{
+    #region Properties
+
+    public bool IsRenaming
+    {
+        get
+        {
+            if (DataContext is not MainViewModel vm)
+            {
+                return false;
+            }
+            return vm.ImageIterator?.IsRenamingInProgress ?? false;
+        }
+        private set
+        {
+            if (DataContext is not MainViewModel vm)
+            {
+                return;
+            }
+            vm.ImageIterator.IsRenamingInProgress = value;
+        }
+    }
+
+    #endregion
+    public EditableTitlebar()
+    {
+        InitializeComponent();
+        LostFocus += OnLostFocus;
+        PointerEntered += OnPointerEntered;
+        PointerPressed += OnPointerPressed;
+    }
+
+    private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
+    {
+        if (DataContext is not MainViewModel vm)
+        {
+            return;
+        }
+
+        if (!e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
+        {
+            return;
+        }
+
+        if (IsRenaming || vm.IsEditableTitlebarOpen)
+        {
+            return;
+        }
+
+        vm.IsEditableTitlebarOpen = true;
+        SelectFileName();
+    }
+
+    private void OnPointerEntered(object? sender, PointerEventArgs e)
+    {
+        if (DataContext is not MainViewModel vm)
+        {
+            return;
+        }
+
+        Cursor = vm.IsEditableTitlebarOpen ?
+            new Cursor(StandardCursorType.Ibeam) :
+            new Cursor(StandardCursorType.Arrow);
+    }
+
+    private void OnLostFocus(object? sender, RoutedEventArgs e)
+    {
+        CloseTitlebar();
+    }
+    
+    public void CloseTitlebar()
+    {
+        TextBox.ClearSelection();
+        if (DataContext is not MainViewModel vm)
+        {
+            return;
+        }
+        SetTitleHelper.RefreshTitle(vm);
+        vm.IsEditableTitlebarOpen = false;
+        Cursor = new Cursor(StandardCursorType.Arrow);
+        MainKeyboardShortcuts.IsKeysEnabled = true;
+        TextBlock.Text = vm.Title;
+    }
+    
+    #region Rename
+    
+    protected override void OnKeyDown(KeyEventArgs e)
+    {
+        MainKeyboardShortcuts.IsKeysEnabled = false;
+        base.OnKeyDown(e);
+    }
+    
+    protected override void OnKeyUp(KeyEventArgs e)
+    {
+        base.OnKeyUp(e);
+        if (e.Key is Key.Enter or Key.Return)
+        {
+            _ = HandleRename();
+            MainKeyboardShortcuts.IsKeysEnabled = true;
+        }
+        else if (e.Key == Key.Escape)
+        {
+            UIHelper.GetMainView.Focus();
+            MainKeyboardShortcuts.IsKeysEnabled = true;
+        }
+    }
+
+    private async Task HandleRename()
+    {
+        if (DataContext is not MainViewModel vm)
+        {
+            return;
+        }
+
+        if (!NavigationHelper.CanNavigate(vm))
+        {
+            return;
+        }
+        
+        if (vm.FileInfo is null)
+        {
+            return;
+        }
+        vm.IsLoading = true;
+        IsRenaming = true;
+        var oldPath = vm.FileInfo.FullName;
+        var newPath = Path.Combine(vm.FileInfo.DirectoryName, TextBox.Text);
+        
+        // Check if the file is being moved to a different directory
+        if (Path.GetDirectoryName(oldPath) != Path.GetDirectoryName(newPath))
+        {
+            await Renamed().ConfigureAwait(false);
+            return;
+        }
+        
+        // Handle renaming with different extensions
+        if (Path.GetExtension(newPath) != Path.GetExtension(oldPath))
+        {
+            var saved = await SaveImageFileHelper.SaveImageAsync(stream: null, path: oldPath, destination: newPath, width:null, height: null, quality: null, ext: Path.GetExtension(newPath)).ConfigureAwait(false);
+            while (FileHelper.IsFileInUse(oldPath))
+            {
+                await Task.Delay(50); // Fixes "this action can't be completed because the file is open"
+            }
+
+            if (saved)
+            {
+                // Navigate to newly saved file
+                await NavigationHelper.LoadPicFromFile(newPath, vm).ConfigureAwait(false);
+                
+                // Delete old file
+                var deleteMsg = FileDeletionHelper.DeleteFileWithErrorMsg(oldPath, false);
+                if (!string.IsNullOrWhiteSpace(deleteMsg))
+                {
+                    // Show error message to user
+                    await TooltipHelper.ShowTooltipMessageAsync(deleteMsg);
+                    vm.IsLoading = false;
+                    return;
+                }
+            }
+            await End();
+            return;
+        }
+        var renamed = await Renamed().ConfigureAwait(false);
+        if (!renamed)
+        {
+            IsRenaming = false;
+            return;
+        }
+        await End();
+        return;
+
+        async Task<bool> Renamed()
+        {
+            return await Task.FromResult(FileHelper.RenameFile(oldPath, newPath));
+        }
+
+        async Task End()
+        {
+            SetTitleHelper.SetTitle(vm);
+            vm.IsLoading = false;
+            IsRenaming = false;
+            await Dispatcher.UIThread.InvokeAsync(() =>
+            {
+                TextBox.ClearSelection();
+                SetTitleHelper.RefreshTitle(vm);
+                vm.IsEditableTitlebarOpen = false;
+                Cursor = new Cursor(StandardCursorType.Arrow);
+                MainKeyboardShortcuts.IsKeysEnabled = true;
+                UIHelper.GetMainView.Focus();
+            });
+        }
+    }
+    
+    public void SelectFileName()
+    {
+        if (DataContext is not MainViewModel vm)
+        {
+            return;
+        }
+
+        if (!NavigationHelper.CanNavigate(vm))
+        {
+            return;
+        }
+        
+        if (vm.FileInfo is null)
+        {
+            return;
+        }
+        TextBox.Text = vm.FileInfo.Name;
+        var filename = vm.FileInfo.Name;
+        var start = TextBox.Text.Length - filename.Length;
+        var end = Path.GetFileNameWithoutExtension(filename).Length;
+        TextBox.SelectionStart = start;
+        TextBox.SelectionEnd = end;
+        vm.IsEditableTitlebarOpen = true;
+        Cursor = new Cursor(StandardCursorType.Ibeam);
+        Focus();
+    }
+
+    #endregion
+}