From 95af64a8d055531126724c4da55d669191ce6b38 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 27 Dec 2018 20:16:50 +0300 Subject: [PATCH 01/31] [WIP] Managed file dialog --- Avalonia.sln | 25 ++ build/CoreLibraries.props | 1 + .../ControlCatalog.NetCore.csproj | 1 + samples/ControlCatalog.NetCore/Program.cs | 41 ++-- src/Avalonia.Controls/AppBuilderBase.cs | 11 + src/Avalonia.Controls/SystemDialog.cs | 16 +- src/Avalonia.Dialogs/Avalonia.Dialogs.csproj | 20 ++ src/Avalonia.Dialogs/Internal/ChildFitter.cs | 20 ++ .../Internal/InternalViewModelBase.cs | 31 +++ .../Internal/ManagedFileChooser.xaml | 74 ++++++ .../Internal/ManagedFileChooser.xaml.cs | 65 +++++ .../ManagedFileChooserFilterViewModel.cs | 38 +++ .../ManagedFileChooserItemViewModel.cs | 45 ++++ .../Internal/ManagedFileChooserSources.cs | 75 ++++++ .../Internal/ManagedFileChooserViewModel.cs | 223 ++++++++++++++++++ .../Internal/ManagedFileDialog.xaml | 7 + .../Internal/ManagedFileDialog.xaml.cs | 18 ++ .../Internal/ResourceSelectorConverter.cs | 21 ++ .../ManagedFileDialogExtensions.cs | 54 +++++ 19 files changed, 765 insertions(+), 21 deletions(-) create mode 100644 src/Avalonia.Dialogs/Avalonia.Dialogs.csproj create mode 100644 src/Avalonia.Dialogs/Internal/ChildFitter.cs create mode 100644 src/Avalonia.Dialogs/Internal/InternalViewModelBase.cs create mode 100644 src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml create mode 100644 src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs create mode 100644 src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs create mode 100644 src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs create mode 100644 src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs create mode 100644 src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs create mode 100644 src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml create mode 100644 src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs create mode 100644 src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs create mode 100644 src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs diff --git a/Avalonia.sln b/Avalonia.sln index df60ff4a75..14ecd76517 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -190,6 +190,7 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Desktop", "src\Avalonia.Desktop\Avalonia.Desktop.csproj", "{3C471044-3640-45E3-B1B2-16D2FF8399EE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -1714,6 +1715,30 @@ Global {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhone.Build.0 = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhone.Build.0 = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhone.Build.0 = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|Any CPU.Build.0 = Release|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhone.ActiveCfg = Release|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhone.Build.0 = Release|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/build/CoreLibraries.props b/build/CoreLibraries.props index d989e643b8..3923bdeeda 100644 --- a/build/CoreLibraries.props +++ b/build/CoreLibraries.props @@ -13,6 +13,7 @@ + diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index 7e2c707e91..f3dce7fc0e 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -6,6 +6,7 @@ + diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 1f53dedc14..2a455cb0b2 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -1,8 +1,13 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Avalonia; +using Avalonia.Controls; +using Avalonia.Dialogs; +using Avalonia.Dialogs.Internal; using Avalonia.Skia; namespace ControlCatalog.NetCore @@ -13,31 +18,31 @@ namespace ControlCatalog.NetCore static void Main(string[] args) { Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA); - if (args.Contains("--wait-for-attach")) + var b = BuildAvaloniaApp(); + b.SetupWithoutStarting(); + var window = new Window(); + window.Show(); + new OpenFileDialog() { - Console.WriteLine("Attach debugger and use 'Set next statement'"); - while (true) + Filters = new List { - Thread.Sleep(100); - if (Debugger.IsAttached) - break; - } - } - if (args.Contains("--fbdev")) - AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => - { - tl.Content = new MainView(); - System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); - }); - else - BuildAvaloniaApp().Start(); + new FileDialogFilter {Name = "All files", Extensions = {"*"}}, + new FileDialogFilter {Name = "Image files", Extensions = {"jpg", "png", "gif"}} + }, + Directory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop), + Title = "My dialog", + InitialFileName = "config.local.json", + AllowMultiple = true + }.ShowAsync(window).ContinueWith(_ => { window.Close(); }, TaskContinuationOptions.ExecuteSynchronously); + + b.Instance.Run(window); } /// /// This method is needed for IDE previewer infrastructure /// public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure().UsePlatformDetect().UseSkia().UseReactiveUI(); + => AppBuilder.Configure().UsePlatformDetect().UseSkia().UseReactiveUI().UseManagedSystemDialogs(); static void ConsoleSilencer() { @@ -46,4 +51,4 @@ namespace ControlCatalog.NetCore Console.ReadKey(true); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 376714b20b..9ee9a2aea5 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; using System.Reflection; using System.Linq; using Avalonia.Platform; @@ -61,6 +62,8 @@ namespace Avalonia.Controls /// public Action BeforeStartCallback { get; private set; } = builder => { }; + public Action AfterPlatformServicesSetupCallback { get; private set; } = builder => { }; + protected AppBuilderBase(IRuntimePlatform platform, Action platformServices) { RuntimePlatform = platform; @@ -110,6 +113,13 @@ namespace Avalonia.Controls AfterSetupCallback = (Action)Delegate.Combine(AfterSetupCallback, callback); return Self; } + + + public TAppBuilder AfterPlatformServicesSetup(Action callback) + { + AfterPlatformServicesSetupCallback = (Action)Delegate.Combine(AfterPlatformServicesSetupCallback, callback); + return Self; + } /// /// Starts the application with an instance of . @@ -275,6 +285,7 @@ namespace Avalonia.Controls RuntimePlatformServicesInitializer(); WindowingSubsystemInitializer(); RenderingSubsystemInitializer(); + AfterPlatformServicesSetupCallback(Self); Instance.RegisterServices(); Instance.Initialize(); AfterSetupCallback(Self); diff --git a/src/Avalonia.Controls/SystemDialog.cs b/src/Avalonia.Controls/SystemDialog.cs index e7cb4763ed..2f03db5aee 100644 --- a/src/Avalonia.Controls/SystemDialog.cs +++ b/src/Avalonia.Controls/SystemDialog.cs @@ -14,7 +14,13 @@ namespace Avalonia.Controls public abstract class FileSystemDialog : SystemDialog { - public string InitialDirectory { get; set; } + [Obsolete("Use Directory")] + public string InitialDirectory + { + get => Directory; + set => Directory = value; + } + public string Directory { get; set; } } public class SaveFileDialog : FileDialog @@ -45,8 +51,12 @@ namespace Avalonia.Controls public class OpenFolderDialog : FileSystemDialog { - public string DefaultDirectory { get; set; } - + [Obsolete("Use Directory")] + public string DefaultDirectory + { + get => Directory; + set => Directory = value; + } public Task ShowAsync(Window parent) { if(parent == null) diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj new file mode 100644 index 0000000000..d5c6b1d920 --- /dev/null +++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj @@ -0,0 +1,20 @@ + + + netstandard2.0 + false + + + + + Designer + + + Designer + + + + + + + + diff --git a/src/Avalonia.Dialogs/Internal/ChildFitter.cs b/src/Avalonia.Dialogs/Internal/ChildFitter.cs new file mode 100644 index 0000000000..d81dafcd99 --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ChildFitter.cs @@ -0,0 +1,20 @@ +using Avalonia.Controls; +using Avalonia.Layout; + +namespace Avalonia.Dialogs.Internal +{ + class ChildFitter : Decorator + { + protected override Size MeasureOverride(Size availableSize) + { + return new Size(0, 0); + } + + protected override Size ArrangeOverride(Size finalSize) + { + Child.Measure(finalSize); + base.ArrangeOverride(finalSize); + return finalSize; + } + } +} diff --git a/src/Avalonia.Dialogs/Internal/InternalViewModelBase.cs b/src/Avalonia.Dialogs/Internal/InternalViewModelBase.cs new file mode 100644 index 0000000000..35e37eb810 --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/InternalViewModelBase.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Avalonia.Dialogs.Internal +{ + class InternalViewModelBase : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected bool RaiseAndSetIfChanged(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (!EqualityComparer.Default.Equals(field, value)) + { + field = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + return true; + } + + return false; + } + + [NotifyPropertyChangedInvocator] + protected void RaisePropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml new file mode 100644 index 0000000000..51154f1161 --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Show hidden files + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs new file mode 100644 index 0000000000..dd23a90922 --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; +using Avalonia.Markup.Xaml; + +namespace Avalonia.Dialogs.Internal +{ + class ManagedFileChooser : UserControl + { + private Control _quickLinksRoot; + private ListBox _filesView; + + public ManagedFileChooser() + { + AvaloniaXamlLoader.Load(this); + AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel); + _quickLinksRoot = this.FindControl("QuickLinks"); + _filesView = this.FindControl("Files"); + } + + ManagedFileChooserViewModel Model => DataContext as ManagedFileChooserViewModel; + + private void OnPointerPressed(object sender, PointerPressedEventArgs e) + { + var model = (e.Source as StyledElement)?.DataContext as ManagedFileChooserItemViewModel; + if(model == null) + return; + + var isQuickLink = _quickLinksRoot.IsLogicalParentOf(e.Source as Control); + if (e.ClickCount == 2 || isQuickLink) + { + if (model.IsDirectory) + Model?.Navigate(model.Path); + else + Model?.SelectSingleFile(model); + e.Handled = true; + } + } + + protected override async void OnDataContextChanged(EventArgs e) + { + base.OnDataContextChanged(e); + var model = (DataContext as ManagedFileChooserViewModel); + if (model == null) + return; + var preselected = model.SelectedItems.FirstOrDefault(); + if(preselected == null) + return; + + //Let everything to settle down and scroll to selected item + await Task.Delay(100); + if (preselected != model.SelectedItems.FirstOrDefault()) + return; + + // Workaround for ListBox bug, scroll to the previous file + var indexOfPreselected = model.Items.IndexOf(preselected); + if (indexOfPreselected > 1) + _filesView.ScrollIntoView(model.Items[indexOfPreselected - 1]); + } + } +} diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs new file mode 100644 index 0000000000..1f561d0cd2 --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls; + +namespace Avalonia.Dialogs.Internal +{ + class ManagedFileChooserFilterViewModel : InternalViewModelBase + { + private readonly string[] _extensions; + public string Name { get; } + + public ManagedFileChooserFilterViewModel(FileDialogFilter filter) + { + Name = filter.Name; + if (filter.Extensions.Contains("*")) + return; + _extensions = filter.Extensions?.Select(e => "." + e.ToLowerInvariant()).ToArray(); + } + + public ManagedFileChooserFilterViewModel() + { + Name = "All files"; + } + + public bool Match(string filename) + { + if (_extensions == null) + return true; + foreach(var ext in _extensions) + if (filename.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase)) + return true; + return false; + } + + public override string ToString() => Name; + } +} diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs new file mode 100644 index 0000000000..d5f72cdbac --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs @@ -0,0 +1,45 @@ +namespace Avalonia.Dialogs.Internal +{ + class ManagedFileChooserItemViewModel : InternalViewModelBase + { + private string _displayName; + private string _path; + private bool _isDirectory; + + public string DisplayName + { + get => _displayName; + set => RaiseAndSetIfChanged(ref _displayName, value); + } + + public string Path + { + get => _path; + set => RaiseAndSetIfChanged(ref _path, value); + } + + public string IconKey => IsDirectory ? "Icon_Folder" : "Icon_File"; + + public bool IsDirectory + { + get => _isDirectory; + set + { + if (RaiseAndSetIfChanged(ref _isDirectory, value)) + RaisePropertyChanged(nameof(IconKey)); + } + } + + public ManagedFileChooserItemViewModel() + { + + } + + public ManagedFileChooserItemViewModel(ManagedFileChooserNavigationItem item) + { + IsDirectory = true; + Path = item.Path; + DisplayName = item.DisplayName; + } + } +} diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs new file mode 100644 index 0000000000..587b32fe03 --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Avalonia.Dialogs.Internal +{ + public class ManagedFileChooserSources + { + public Func GetUserDirectories { get; set; } + = DefaultGetUserDirectories; + + public Func GetFileSystemRoots { get; set; } + = DefaultGetFileSystemRoots; + + public Func GetAllItemsDelegate { get; set; } + = DefaultGetAllItems; + + public ManagedFileChooserNavigationItem[] GetAllItems() => GetAllItemsDelegate(this); + + public static ManagedFileChooserNavigationItem[] DefaultGetAllItems(ManagedFileChooserSources sources) + { + return sources.GetUserDirectories().Concat(sources.GetFileSystemRoots()).ToArray(); + } + + private static Environment.SpecialFolder[] s_folders = new[] + { + Environment.SpecialFolder.Desktop, + Environment.SpecialFolder.UserProfile, + Environment.SpecialFolder.MyDocuments, + Environment.SpecialFolder.MyMusic, + Environment.SpecialFolder.MyPictures, + Environment.SpecialFolder.MyVideos + }; + + public static ManagedFileChooserNavigationItem[] DefaultGetUserDirectories() + { + return s_folders.Select(Environment.GetFolderPath).Distinct() + .Where(d => !string.IsNullOrWhiteSpace(d)) + .Where(Directory.Exists) + .Select(d => new ManagedFileChooserNavigationItem + { + Path = d, + DisplayName = Path.GetFileName(d) + }).ToArray(); + } + + public static ManagedFileChooserNavigationItem[] DefaultGetFileSystemRoots() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return DriveInfo.GetDrives().Select(d => new ManagedFileChooserNavigationItem + { + DisplayName = d.Name, + Path = d.RootDirectory.FullName + }).ToArray(); + } + + return new[] + { + new ManagedFileChooserNavigationItem + { + DisplayName = "File System", + Path = "/" + } + }; + } + } + + public class ManagedFileChooserNavigationItem + { + public string DisplayName { get; set; } + public string Path { get; set; } + } +} diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs new file mode 100644 index 0000000000..72bff302bd --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Threading; + +namespace Avalonia.Dialogs.Internal +{ + class ManagedFileChooserViewModel : InternalViewModelBase + { + public event Action CancelRequested; + public event Action CompleteRequested; + + public AvaloniaList QuickLinks { get; } = + new AvaloniaList(); + + public AvaloniaList Items { get; } = + new AvaloniaList(); + + public AvaloniaList Filters { get; } = + new AvaloniaList(); + + public AvaloniaList SelectedItems { get; } = + new AvaloniaList(); + + string _location; + private bool _showHiddenFiles; + private ManagedFileChooserFilterViewModel _selectedFilter; + private bool _selectingDirectory; + private bool _scheduledSelectionValidation; + + public string Location + { + get => _location; + private set => RaiseAndSetIfChanged(ref _location, value); + } + + public bool ShowFilters { get; } + public SelectionMode SelectionMode { get; } + public string Title { get; } + + public int QuickLinksSelectedIndex + { + get + { + for (var index = 0; index < QuickLinks.Count; index++) + { + var i = QuickLinks[index]; + if (i.Path == Location) + return index; + } + + return -1; + } + set => RaisePropertyChanged(nameof(QuickLinksSelectedIndex)); + } + + public ManagedFileChooserFilterViewModel SelectedFilter + { + get => _selectedFilter; + set + { + RaiseAndSetIfChanged(ref _selectedFilter, value); + Refresh(); + } + } + + public bool ShowHiddenFiles + { + get => _showHiddenFiles; + set + { + RaiseAndSetIfChanged(ref _showHiddenFiles, value); + Refresh(); + } + } + + public ManagedFileChooserViewModel(FileSystemDialog dialog) + { + var quickSources = AvaloniaLocator.Current.GetService() + ?? new ManagedFileChooserSources(); + QuickLinks.Clear(); + + QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i))); + Title = dialog.Title ?? ( + dialog is OpenFileDialog ? "Open file" + : dialog is SaveFileDialog ? "Save file" + : dialog is OpenFolderDialog ? "Select directory" + : throw new ArgumentException(nameof(dialog))); + + var directory = dialog.Directory; + if (directory == null || !Directory.Exists(directory)) + directory = Directory.GetCurrentDirectory(); + + if (dialog is FileDialog fd) + { + if (fd.Filters?.Count > 0) + { + Filters.AddRange(fd.Filters.Select(f => new ManagedFileChooserFilterViewModel(f))); + _selectedFilter = Filters[0]; + ShowFilters = true; + } + + if (dialog is OpenFileDialog ofd) + { + if (ofd.AllowMultiple) + SelectionMode = SelectionMode.Multiple; + } + } + + _selectingDirectory = dialog is OpenFolderDialog; + + Navigate(directory, (dialog as FileDialog)?.InitialFileName); + SelectedItems.CollectionChanged += OnSelectionChanged; + } + + private async void OnSelectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if(_scheduledSelectionValidation) + return; + _scheduledSelectionValidation = true; + await Dispatcher.UIThread.InvokeAsync(() => + { + try + { + if(_selectingDirectory) + SelectedItems.Clear(); + else + { + var invalidItems = SelectedItems.Where(i => i.IsDirectory).ToList(); + foreach (var item in invalidItems) + SelectedItems.Remove(item); + } + } + finally + { + _scheduledSelectionValidation = false; + } + }); + } + + void NavigateRoot(string initialSelectionName) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + Navigate(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)), initialSelectionName); + else + Navigate("/", initialSelectionName); + } + + public void Refresh() => Navigate(Location); + + public void Navigate(string path, string initialSelectionName = null) + { + if (!Directory.Exists(path)) + NavigateRoot(initialSelectionName); + else + { + Location = path; + Items.Clear(); + SelectedItems.Clear(); + var infos = new DirectoryInfo(path).EnumerateFileSystemInfos(); + if (!ShowHiddenFiles) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + infos = infos.Where(i => (i.Attributes & (FileAttributes.Hidden | FileAttributes.System)) != 0); + else + infos = infos.Where(i => !i.Name.StartsWith(".")); + } + + if (SelectedFilter != null) + infos = infos.Where(i => i is DirectoryInfo || SelectedFilter.Match(i.Name)); + + Items.AddRange(infos.Select(info => new ManagedFileChooserItemViewModel + { + DisplayName = info.Name, + Path = info.FullName, + IsDirectory = info is DirectoryInfo + }).OrderByDescending(x => x.IsDirectory) + .ThenBy(x => x.DisplayName, StringComparer.InvariantCultureIgnoreCase)); + + if (initialSelectionName != null) + { + var sel = Items.FirstOrDefault(i => !i.IsDirectory && i.DisplayName == initialSelectionName); + if (sel != null) + SelectedItems.Add(sel); + } + + RaisePropertyChanged(nameof(QuickLinksSelectedIndex)); + } + + + } + + public void GoUp() + { + var parent = Path.GetDirectoryName(Location); + if (string.IsNullOrWhiteSpace(parent)) + return; + Navigate(parent); + } + + public void Cancel() + { + CancelRequested?.Invoke(); + } + + public void Ok() + { + if (_selectingDirectory) + CompleteRequested?.Invoke(new[] {Location}); + else + CompleteRequested?.Invoke(SelectedItems.Select(i => i.Path).ToArray()); + } + + public void SelectSingleFile(ManagedFileChooserItemViewModel item) + { + CompleteRequested?.Invoke(new[] {item.Path}); + } + } +} diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml new file mode 100644 index 0000000000..34c8dba363 --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml @@ -0,0 +1,7 @@ + + + diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs new file mode 100644 index 0000000000..43276f5e90 --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs @@ -0,0 +1,18 @@ +using System; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Avalonia.Dialogs.Internal +{ + class ManagedFileDialog : Window + { + private ManagedFileChooserViewModel _model; + public ManagedFileDialog() + { + AvaloniaXamlLoader.Load(this); + #if DEBUG + this.AttachDevTools(); + #endif + } + } +} diff --git a/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs b/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs new file mode 100644 index 0000000000..a492dfed3a --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.Globalization; +using Avalonia.Controls; +using Avalonia.Data.Converters; + +namespace Avalonia.Dialogs.Internal +{ + public class ResourceSelectorConverter : ResourceDictionary, IValueConverter + { + public object Convert(object key, Type targetType, object parameter, CultureInfo culture) + { + TryGetResource((string)key, out var value); + return value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs new file mode 100644 index 0000000000..26a11fc4ff --- /dev/null +++ b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs @@ -0,0 +1,54 @@ +using System.Linq; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Dialogs.Internal; +using Avalonia.Platform; + +namespace Avalonia.Dialogs +{ + public static class ManagedFileDialogExtensions + { + class ManagedSystemDialogImpl : ISystemDialogImpl + { + async Task Show(SystemDialog d, IWindowImpl parent) + { + var model = new ManagedFileChooserViewModel((FileSystemDialog)d); + + var dialog = new ManagedFileDialog + { + DataContext = model + }; + + string[] result = null; + model.CompleteRequested += items => + { + result = items; + dialog.Close(); + }; + model.CancelRequested += dialog.Close; + + await dialog.ShowDialog(parent); + return result; + } + + public async Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) + { + return await Show(dialog, parent); + } + + public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + return (await Show(dialog, parent))?.FirstOrDefault(); + } + } + + public static TAppBuilder UseManagedSystemDialogs(this TAppBuilder builder) + where TAppBuilder : AppBuilderBase, new() + { + builder.AfterPlatformServicesSetup(_ => + AvaloniaLocator.CurrentMutable.Bind().ToSingleton()); + return builder; + } + } +} From 44803ef9dd17e19fd6b676a08ab3c0b3feabdb9f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 Jul 2019 21:41:33 +0100 Subject: [PATCH 02/31] back port managed dialog work from wasabi wallet. --- Avalonia.sln | 10 +- build/ReactiveUI.props | 2 +- build/Rx.props | 2 +- src/Avalonia.Dialogs/Avalonia.Dialogs.csproj | 5 +- .../Internal/ByteSizeHelper.cs | 29 + src/Avalonia.Dialogs/Internal/ChildFitter.cs | 27 +- .../Internal/FileSizeStringConverter.cs | 26 + .../Internal/ManagedFileChooser.xaml | 170 ++++-- .../Internal/ManagedFileChooser.xaml.cs | 129 +++-- .../ManagedFileChooserFilterViewModel.cs | 72 ++- .../ManagedFileChooserItemViewModel.cs | 108 ++-- .../ManagedFileChooserNavigationItem.cs | 8 + .../Internal/ManagedFileChooserSources.cs | 134 +++-- .../Internal/ManagedFileChooserViewModel.cs | 532 +++++++++++------- .../Internal/ManagedFileDialog.xaml | 7 +- .../Internal/ManagedFileDialog.xaml.cs | 24 +- .../Internal/ResourceSelectorConverter.cs | 24 +- .../ManagedFileDialogExtensions.cs | 81 +-- 18 files changed, 852 insertions(+), 538 deletions(-) create mode 100644 src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs create mode 100644 src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs create mode 100644 src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs diff --git a/Avalonia.sln b/Avalonia.sln index cb9669fe6b..bf7710d7e8 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2027 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29102.190 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}" EndProject @@ -197,9 +197,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "sam EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}" -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props index 1208be34b8..3baf83f6b5 100644 --- a/build/ReactiveUI.props +++ b/build/ReactiveUI.props @@ -1,5 +1,5 @@ - + diff --git a/build/Rx.props b/build/Rx.props index 359ce53a92..d40849686f 100644 --- a/build/Rx.props +++ b/build/Rx.props @@ -1,5 +1,5 @@  - + diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj index d5c6b1d920..b94b1db8bc 100644 --- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj +++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 false @@ -13,6 +13,9 @@ + + + diff --git a/src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs b/src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs new file mode 100644 index 0000000000..3517f760b4 --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Dialogs.Internal +{ + public static class ByteSizeHelper + { + private static readonly string[] Prefixes = + { + "B", + "KB", + "MB", + "GB", + "TB" + }; + + public static string ToString(long bytes) + { + var index = 0; + while (bytes >= 1000) + { + bytes /= 1000; + ++index; + } + return $"{bytes:N} {Prefixes[index]}"; + } + } +} diff --git a/src/Avalonia.Dialogs/Internal/ChildFitter.cs b/src/Avalonia.Dialogs/Internal/ChildFitter.cs index d81dafcd99..6f9baff82f 100644 --- a/src/Avalonia.Dialogs/Internal/ChildFitter.cs +++ b/src/Avalonia.Dialogs/Internal/ChildFitter.cs @@ -1,20 +1,21 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Layout; namespace Avalonia.Dialogs.Internal { - class ChildFitter : Decorator - { - protected override Size MeasureOverride(Size availableSize) - { - return new Size(0, 0); - } + class ChildFitter : Decorator + { + protected override Size MeasureOverride(Size availableSize) + { + return new Size(0, 0); + } - protected override Size ArrangeOverride(Size finalSize) - { - Child.Measure(finalSize); - base.ArrangeOverride(finalSize); - return finalSize; - } - } + protected override Size ArrangeOverride(Size finalSize) + { + Child.Measure(finalSize); + base.ArrangeOverride(finalSize); + return finalSize; + } + } } diff --git a/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs b/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs new file mode 100644 index 0000000000..6de931c89d --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs @@ -0,0 +1,26 @@ +using Avalonia.Data.Converters; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; + +namespace Avalonia.Dialogs.Internal +{ + public class FileSizeStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if(value is long size && size > 0) + { + return ByteSizeHelper.ToString(size); + } + + return ""; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml index 51154f1161..cb7f2bef34 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml @@ -4,71 +4,127 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal" xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Show hidden files - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Show hidden files + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SelectedItems="{Binding SelectedItems}" + ScrollViewer.HorizontalScrollBarVisibility="Disabled"> + + + + + + + + + + + + + + + + + + + + + + + - + + diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs index dd23a90922..7aba87115d 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; @@ -9,57 +10,79 @@ using Avalonia.Markup.Xaml; namespace Avalonia.Dialogs.Internal { - class ManagedFileChooser : UserControl - { - private Control _quickLinksRoot; - private ListBox _filesView; - - public ManagedFileChooser() - { - AvaloniaXamlLoader.Load(this); - AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel); - _quickLinksRoot = this.FindControl("QuickLinks"); - _filesView = this.FindControl("Files"); - } - - ManagedFileChooserViewModel Model => DataContext as ManagedFileChooserViewModel; - - private void OnPointerPressed(object sender, PointerPressedEventArgs e) - { - var model = (e.Source as StyledElement)?.DataContext as ManagedFileChooserItemViewModel; - if(model == null) - return; - - var isQuickLink = _quickLinksRoot.IsLogicalParentOf(e.Source as Control); - if (e.ClickCount == 2 || isQuickLink) - { - if (model.IsDirectory) - Model?.Navigate(model.Path); - else - Model?.SelectSingleFile(model); - e.Handled = true; - } - } - - protected override async void OnDataContextChanged(EventArgs e) - { - base.OnDataContextChanged(e); - var model = (DataContext as ManagedFileChooserViewModel); - if (model == null) - return; - var preselected = model.SelectedItems.FirstOrDefault(); - if(preselected == null) - return; - - //Let everything to settle down and scroll to selected item - await Task.Delay(100); - if (preselected != model.SelectedItems.FirstOrDefault()) - return; - - // Workaround for ListBox bug, scroll to the previous file - var indexOfPreselected = model.Items.IndexOf(preselected); - if (indexOfPreselected > 1) - _filesView.ScrollIntoView(model.Items[indexOfPreselected - 1]); - } - } + class ManagedFileChooser : UserControl + { + private Control _quickLinksRoot; + private ListBox _filesView; + + public ManagedFileChooser() + { + AvaloniaXamlLoader.Load(this); + AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel); + _quickLinksRoot = this.FindControl("QuickLinks"); + _filesView = this.FindControl("Files"); + } + + ManagedFileChooserViewModel Model => DataContext as ManagedFileChooserViewModel; + + private void OnPointerPressed(object sender, PointerPressedEventArgs e) + { + var model = (e.Source as StyledElement)?.DataContext as ManagedFileChooserItemViewModel; + + if (model == null) + { + return; + } + + var isQuickLink = _quickLinksRoot.IsLogicalParentOf(e.Source as Control); + if (e.ClickCount == 2 || isQuickLink) + { + if (model.IsDirectory) + { + Model?.Navigate(model.Path); + } + else + { + Model?.SelectSingleFile(model); + } + + e.Handled = true; + } + } + + protected override async void OnDataContextChanged(EventArgs e) + { + base.OnDataContextChanged(e); + + var model = (DataContext as ManagedFileChooserViewModel); + + if (model == null) + { + return; + } + + var preselected = model.SelectedItems.FirstOrDefault(); + + if (preselected == null) + { + return; + } + + //Let everything to settle down and scroll to selected item + await Task.Delay(100); + + if (preselected != model.SelectedItems.FirstOrDefault()) + { + return; + } + + // Workaround for ListBox bug, scroll to the previous file + var indexOfPreselected = model.Items.IndexOf(preselected); + + if (indexOfPreselected > 1) + { + _filesView.ScrollIntoView(model.Items[indexOfPreselected - 1]); + } + } + } } diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs index 1f561d0cd2..d54945ac60 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs @@ -5,34 +5,46 @@ using Avalonia.Controls; namespace Avalonia.Dialogs.Internal { - class ManagedFileChooserFilterViewModel : InternalViewModelBase - { - private readonly string[] _extensions; - public string Name { get; } - - public ManagedFileChooserFilterViewModel(FileDialogFilter filter) - { - Name = filter.Name; - if (filter.Extensions.Contains("*")) - return; - _extensions = filter.Extensions?.Select(e => "." + e.ToLowerInvariant()).ToArray(); - } - - public ManagedFileChooserFilterViewModel() - { - Name = "All files"; - } - - public bool Match(string filename) - { - if (_extensions == null) - return true; - foreach(var ext in _extensions) - if (filename.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase)) - return true; - return false; - } - - public override string ToString() => Name; - } + class ManagedFileChooserFilterViewModel : InternalViewModelBase + { + private readonly string[] _extensions; + public string Name { get; } + + public ManagedFileChooserFilterViewModel(FileDialogFilter filter) + { + Name = filter.Name; + + if (filter.Extensions.Contains("*")) + { + return; + } + + _extensions = filter.Extensions?.Select(e => "." + e.ToLowerInvariant()).ToArray(); + } + + public ManagedFileChooserFilterViewModel() + { + Name = "All files"; + } + + public bool Match(string filename) + { + if (_extensions == null) + { + return true; + } + + foreach (var ext in _extensions) + { + if (filename.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase)) + { + return true; + } + } + + return false; + } + + public override string ToString() => Name; + } } diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs index d5f72cdbac..b6535a93d0 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs @@ -1,45 +1,69 @@ +using System; + namespace Avalonia.Dialogs.Internal { - class ManagedFileChooserItemViewModel : InternalViewModelBase - { - private string _displayName; - private string _path; - private bool _isDirectory; - - public string DisplayName - { - get => _displayName; - set => RaiseAndSetIfChanged(ref _displayName, value); - } - - public string Path - { - get => _path; - set => RaiseAndSetIfChanged(ref _path, value); - } - - public string IconKey => IsDirectory ? "Icon_Folder" : "Icon_File"; - - public bool IsDirectory - { - get => _isDirectory; - set - { - if (RaiseAndSetIfChanged(ref _isDirectory, value)) - RaisePropertyChanged(nameof(IconKey)); - } - } - - public ManagedFileChooserItemViewModel() - { - - } - - public ManagedFileChooserItemViewModel(ManagedFileChooserNavigationItem item) - { - IsDirectory = true; - Path = item.Path; - DisplayName = item.DisplayName; - } - } + class ManagedFileChooserItemViewModel : InternalViewModelBase + { + private string _displayName; + private string _path; + private bool _isDirectory; + private DateTime _modified; + private string _type; + private long _size; + + public string DisplayName + { + get => _displayName; + set => this.RaiseAndSetIfChanged(ref _displayName, value); + } + + public string Path + { + get => _path; + set => this.RaiseAndSetIfChanged(ref _path, value); + } + + public DateTime Modified + { + get => _modified; + set => this.RaiseAndSetIfChanged(ref _modified, value); + } + + public string Type + { + get => _type; + set => this.RaiseAndSetIfChanged(ref _type, value); + } + + public long Size + { + get => _size; + set => this.RaiseAndSetIfChanged(ref _size, value); + } + + public string IconKey => IsDirectory ? "Icon_Folder" : "Icon_File"; + + public bool IsDirectory + { + get => _isDirectory; + set + { + if (this.RaiseAndSetIfChanged(ref _isDirectory, value)) + { + this.RaisePropertyChanged(nameof(IconKey)); + } + } + } + + public ManagedFileChooserItemViewModel() + { + } + + public ManagedFileChooserItemViewModel(ManagedFileChooserNavigationItem item) + { + IsDirectory = true; + Path = item.Path; + DisplayName = item.DisplayName; + } + } } diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs new file mode 100644 index 0000000000..b04a36692c --- /dev/null +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs @@ -0,0 +1,8 @@ +namespace Avalonia.Dialogs.Internal +{ + public class ManagedFileChooserNavigationItem + { + public string DisplayName { get; set; } + public string Path { get; set; } + } +} diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs index 587b32fe03..84a3173eac 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs @@ -5,71 +5,85 @@ using System.Runtime.InteropServices; namespace Avalonia.Dialogs.Internal { - public class ManagedFileChooserSources - { - public Func GetUserDirectories { get; set; } - = DefaultGetUserDirectories; + public class ManagedFileChooserSources + { + public Func GetUserDirectories { get; set; } + = DefaultGetUserDirectories; - public Func GetFileSystemRoots { get; set; } - = DefaultGetFileSystemRoots; + public Func GetFileSystemRoots { get; set; } + = DefaultGetFileSystemRoots; - public Func GetAllItemsDelegate { get; set; } - = DefaultGetAllItems; + public Func GetAllItemsDelegate { get; set; } + = DefaultGetAllItems; - public ManagedFileChooserNavigationItem[] GetAllItems() => GetAllItemsDelegate(this); + public ManagedFileChooserNavigationItem[] GetAllItems() => GetAllItemsDelegate(this); - public static ManagedFileChooserNavigationItem[] DefaultGetAllItems(ManagedFileChooserSources sources) - { - return sources.GetUserDirectories().Concat(sources.GetFileSystemRoots()).ToArray(); - } - - private static Environment.SpecialFolder[] s_folders = new[] - { - Environment.SpecialFolder.Desktop, - Environment.SpecialFolder.UserProfile, - Environment.SpecialFolder.MyDocuments, - Environment.SpecialFolder.MyMusic, - Environment.SpecialFolder.MyPictures, - Environment.SpecialFolder.MyVideos - }; - - public static ManagedFileChooserNavigationItem[] DefaultGetUserDirectories() - { - return s_folders.Select(Environment.GetFolderPath).Distinct() - .Where(d => !string.IsNullOrWhiteSpace(d)) - .Where(Directory.Exists) - .Select(d => new ManagedFileChooserNavigationItem - { - Path = d, - DisplayName = Path.GetFileName(d) - }).ToArray(); - } + public static ManagedFileChooserNavigationItem[] DefaultGetAllItems(ManagedFileChooserSources sources) + { + return sources.GetUserDirectories().Concat(sources.GetFileSystemRoots()).ToArray(); + } - public static ManagedFileChooserNavigationItem[] DefaultGetFileSystemRoots() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return DriveInfo.GetDrives().Select(d => new ManagedFileChooserNavigationItem - { - DisplayName = d.Name, - Path = d.RootDirectory.FullName - }).ToArray(); - } + private static Environment.SpecialFolder[] s_folders = new[] + { + Environment.SpecialFolder.Desktop, + Environment.SpecialFolder.UserProfile, + Environment.SpecialFolder.MyDocuments, + Environment.SpecialFolder.MyMusic, + Environment.SpecialFolder.MyPictures, + Environment.SpecialFolder.MyVideos + }; - return new[] - { - new ManagedFileChooserNavigationItem - { - DisplayName = "File System", - Path = "/" - } - }; - } - } + public static ManagedFileChooserNavigationItem[] DefaultGetUserDirectories() + { + return s_folders.Select(Environment.GetFolderPath).Distinct() + .Where(d => !string.IsNullOrWhiteSpace(d)) + .Where(Directory.Exists) + .Select(d => new ManagedFileChooserNavigationItem + { + Path = d, + DisplayName = Path.GetFileName(d) + }).ToArray(); + } - public class ManagedFileChooserNavigationItem - { - public string DisplayName { get; set; } - public string Path { get; set; } - } + public static ManagedFileChooserNavigationItem[] DefaultGetFileSystemRoots() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return DriveInfo.GetDrives().Select(d => new ManagedFileChooserNavigationItem + { + DisplayName = d.Name, + Path = d.RootDirectory.FullName + }).ToArray(); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var paths = Directory.GetDirectories("/Volumes"); + + return paths.Select(x => new ManagedFileChooserNavigationItem + { + DisplayName = Path.GetFileName(x), + Path = x + }).ToArray(); + } + else + { + var paths = Directory.GetDirectories("/media/"); + + var drives = new ManagedFileChooserNavigationItem[] + { + new ManagedFileChooserNavigationItem + { + DisplayName = "File System", + Path = "/" + } + }.Concat(paths.Select(x => new ManagedFileChooserNavigationItem + { + DisplayName = Path.GetFileName(x), + Path = x + })).ToArray(); + + return drives; + } + } + } } diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs index 72bff302bd..8f557a212d 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs @@ -2,222 +2,334 @@ using System; using System.Collections.Specialized; using System.IO; using System.Linq; +using System.Reactive; using System.Runtime.InteropServices; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Threading; +using ReactiveUI; namespace Avalonia.Dialogs.Internal { class ManagedFileChooserViewModel : InternalViewModelBase - { - public event Action CancelRequested; - public event Action CompleteRequested; - - public AvaloniaList QuickLinks { get; } = - new AvaloniaList(); - - public AvaloniaList Items { get; } = - new AvaloniaList(); - - public AvaloniaList Filters { get; } = - new AvaloniaList(); - - public AvaloniaList SelectedItems { get; } = - new AvaloniaList(); - - string _location; - private bool _showHiddenFiles; - private ManagedFileChooserFilterViewModel _selectedFilter; - private bool _selectingDirectory; - private bool _scheduledSelectionValidation; - - public string Location - { - get => _location; - private set => RaiseAndSetIfChanged(ref _location, value); - } - - public bool ShowFilters { get; } - public SelectionMode SelectionMode { get; } - public string Title { get; } - - public int QuickLinksSelectedIndex - { - get - { - for (var index = 0; index < QuickLinks.Count; index++) - { - var i = QuickLinks[index]; - if (i.Path == Location) - return index; - } - - return -1; - } - set => RaisePropertyChanged(nameof(QuickLinksSelectedIndex)); - } - - public ManagedFileChooserFilterViewModel SelectedFilter - { - get => _selectedFilter; - set - { - RaiseAndSetIfChanged(ref _selectedFilter, value); - Refresh(); - } - } - - public bool ShowHiddenFiles - { - get => _showHiddenFiles; - set - { - RaiseAndSetIfChanged(ref _showHiddenFiles, value); - Refresh(); - } - } - - public ManagedFileChooserViewModel(FileSystemDialog dialog) - { - var quickSources = AvaloniaLocator.Current.GetService() - ?? new ManagedFileChooserSources(); - QuickLinks.Clear(); - - QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i))); - Title = dialog.Title ?? ( - dialog is OpenFileDialog ? "Open file" - : dialog is SaveFileDialog ? "Save file" - : dialog is OpenFolderDialog ? "Select directory" - : throw new ArgumentException(nameof(dialog))); - - var directory = dialog.Directory; - if (directory == null || !Directory.Exists(directory)) - directory = Directory.GetCurrentDirectory(); - - if (dialog is FileDialog fd) - { - if (fd.Filters?.Count > 0) - { - Filters.AddRange(fd.Filters.Select(f => new ManagedFileChooserFilterViewModel(f))); - _selectedFilter = Filters[0]; - ShowFilters = true; - } - - if (dialog is OpenFileDialog ofd) - { - if (ofd.AllowMultiple) - SelectionMode = SelectionMode.Multiple; - } - } - - _selectingDirectory = dialog is OpenFolderDialog; - - Navigate(directory, (dialog as FileDialog)?.InitialFileName); - SelectedItems.CollectionChanged += OnSelectionChanged; - } - - private async void OnSelectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if(_scheduledSelectionValidation) - return; - _scheduledSelectionValidation = true; - await Dispatcher.UIThread.InvokeAsync(() => - { - try - { - if(_selectingDirectory) - SelectedItems.Clear(); - else - { - var invalidItems = SelectedItems.Where(i => i.IsDirectory).ToList(); - foreach (var item in invalidItems) - SelectedItems.Remove(item); - } - } - finally - { - _scheduledSelectionValidation = false; - } - }); - } - - void NavigateRoot(string initialSelectionName) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - Navigate(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)), initialSelectionName); - else - Navigate("/", initialSelectionName); - } - - public void Refresh() => Navigate(Location); - - public void Navigate(string path, string initialSelectionName = null) - { - if (!Directory.Exists(path)) - NavigateRoot(initialSelectionName); - else - { - Location = path; - Items.Clear(); - SelectedItems.Clear(); - var infos = new DirectoryInfo(path).EnumerateFileSystemInfos(); - if (!ShowHiddenFiles) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - infos = infos.Where(i => (i.Attributes & (FileAttributes.Hidden | FileAttributes.System)) != 0); - else - infos = infos.Where(i => !i.Name.StartsWith(".")); - } - - if (SelectedFilter != null) - infos = infos.Where(i => i is DirectoryInfo || SelectedFilter.Match(i.Name)); - - Items.AddRange(infos.Select(info => new ManagedFileChooserItemViewModel - { - DisplayName = info.Name, - Path = info.FullName, - IsDirectory = info is DirectoryInfo - }).OrderByDescending(x => x.IsDirectory) - .ThenBy(x => x.DisplayName, StringComparer.InvariantCultureIgnoreCase)); - - if (initialSelectionName != null) - { - var sel = Items.FirstOrDefault(i => !i.IsDirectory && i.DisplayName == initialSelectionName); - if (sel != null) - SelectedItems.Add(sel); - } - - RaisePropertyChanged(nameof(QuickLinksSelectedIndex)); - } - - - } - - public void GoUp() - { - var parent = Path.GetDirectoryName(Location); - if (string.IsNullOrWhiteSpace(parent)) - return; - Navigate(parent); - } - - public void Cancel() - { - CancelRequested?.Invoke(); - } - - public void Ok() - { - if (_selectingDirectory) - CompleteRequested?.Invoke(new[] {Location}); - else - CompleteRequested?.Invoke(SelectedItems.Select(i => i.Path).ToArray()); - } - - public void SelectSingleFile(ManagedFileChooserItemViewModel item) - { - CompleteRequested?.Invoke(new[] {item.Path}); - } - } + { + public event Action CancelRequested; + public event Action CompleteRequested; + + public AvaloniaList QuickLinks { get; } = + new AvaloniaList(); + + public AvaloniaList Items { get; } = + new AvaloniaList(); + + public AvaloniaList Filters { get; } = + new AvaloniaList(); + + public AvaloniaList SelectedItems { get; } = + new AvaloniaList(); + + string _location; + string _fileName; + private bool _showHiddenFiles; + private ManagedFileChooserFilterViewModel _selectedFilter; + private bool _selectingDirectory; + private bool _savingFile; + private bool _scheduledSelectionValidation; + private string _defaultExtension; + + public string Location + { + get => _location; + private set => this.RaiseAndSetIfChanged(ref _location, value); + } + + public string FileName + { + get => _fileName; + private set => this.RaiseAndSetIfChanged(ref _fileName, value); + } + + public bool SelectingFolder => _selectingDirectory; + + public bool ShowFilters { get; } + public SelectionMode SelectionMode { get; } + public string Title { get; } + + public int QuickLinksSelectedIndex + { + get + { + for (var index = 0; index < QuickLinks.Count; index++) + { + var i = QuickLinks[index]; + + if (i.Path == Location) + { + return index; + } + } + + return -1; + } + set => this.RaisePropertyChanged(nameof(QuickLinksSelectedIndex)); + } + + public ManagedFileChooserFilterViewModel SelectedFilter + { + get => _selectedFilter; + set + { + this.RaiseAndSetIfChanged(ref _selectedFilter, value); + Refresh(); + } + } + + public bool ShowHiddenFiles + { + get => _showHiddenFiles; + set + { + this.RaiseAndSetIfChanged(ref _showHiddenFiles, value); + Refresh(); + } + } + + public ManagedFileChooserViewModel(FileSystemDialog dialog) + { + var quickSources = AvaloniaLocator.Current.GetService() + ?? new ManagedFileChooserSources(); + + QuickLinks.Clear(); + + QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i))); + + Title = dialog.Title ?? ( + dialog is OpenFileDialog ? "Open file" + : dialog is SaveFileDialog ? "Save file" + : dialog is OpenFolderDialog ? "Select directory" + : throw new ArgumentException(nameof(dialog))); + + var directory = dialog.InitialDirectory; + + if (directory == null || !Directory.Exists(directory)) + { + directory = Directory.GetCurrentDirectory(); + } + + if (dialog is FileDialog fd) + { + if (fd.Filters?.Count > 0) + { + Filters.AddRange(fd.Filters.Select(f => new ManagedFileChooserFilterViewModel(f))); + _selectedFilter = Filters[0]; + ShowFilters = true; + } + + if (dialog is OpenFileDialog ofd) + { + if (ofd.AllowMultiple) + { + SelectionMode = SelectionMode.Multiple; + } + } + } + + _selectingDirectory = dialog is OpenFolderDialog; + + if(dialog is SaveFileDialog sfd) + { + _savingFile = true; + _defaultExtension = sfd.DefaultExtension; + FileName = sfd.InitialFileName; + } + + Navigate(directory, (dialog as FileDialog)?.InitialFileName); + SelectedItems.CollectionChanged += OnSelectionChangedAsync; + + EnterLocationCommand = ReactiveCommand.Create(() => + { + if (Directory.Exists(Location)) + { + Navigate(Location); + } + else if (File.Exists(Location)) + { + CompleteRequested?.Invoke(new[] { Location }); + } + }); + } + + private async void OnSelectionChangedAsync(object sender, NotifyCollectionChangedEventArgs e) + { + if (_scheduledSelectionValidation) + { + return; + } + + _scheduledSelectionValidation = true; + await Dispatcher.UIThread.InvokeAsync(() => + { + try + { + if (_selectingDirectory) + { + SelectedItems.Clear(); + } + else + { + var invalidItems = SelectedItems.Where(i => i.IsDirectory).ToList(); + foreach (var item in invalidItems) + { + SelectedItems.Remove(item); + } + + if(!_selectingDirectory) + { + FileName = SelectedItems.FirstOrDefault()?.DisplayName; + } + } + } + finally + { + _scheduledSelectionValidation = false; + } + }); + } + + void NavigateRoot(string initialSelectionName) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Navigate(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)), initialSelectionName); + } + else + { + Navigate("/", initialSelectionName); + } + } + + public void Refresh() => Navigate(Location); + + public void Navigate(string path, string initialSelectionName = null) + { + if (!Directory.Exists(path)) + { + NavigateRoot(initialSelectionName); + } + else + { + Location = path; + Items.Clear(); + SelectedItems.Clear(); + + try + { + var infos = new DirectoryInfo(path).EnumerateFileSystemInfos(); + + if (!ShowHiddenFiles) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + infos = infos.Where(i => (i.Attributes & (FileAttributes.Hidden | FileAttributes.System)) != 0); + } + else + { + infos = infos.Where(i => !i.Name.StartsWith(".")); + } + } + + if (SelectedFilter != null) + { + infos = infos.Where(i => i is DirectoryInfo || SelectedFilter.Match(i.Name)); + } + + Items.AddRange(infos.Where(x => + { + if (_selectingDirectory) + { + if (!(x is DirectoryInfo)) + { + return false; + } + } + + return true; + }).Select(info => new ManagedFileChooserItemViewModel + { + DisplayName = info.Name, + Path = info.FullName, + IsDirectory = info is DirectoryInfo, + Type = info is FileInfo ? info.Extension : "File Folder", + Size = info is FileInfo f ? f.Length : 0, + Modified = info.LastWriteTime + }) + .OrderByDescending(x => x.IsDirectory) + .ThenBy(x => x.DisplayName, StringComparer.InvariantCultureIgnoreCase)); + + if (initialSelectionName != null) + { + var sel = Items.FirstOrDefault(i => !i.IsDirectory && i.DisplayName == initialSelectionName); + + if (sel != null) + { + SelectedItems.Add(sel); + } + } + + this.RaisePropertyChanged(nameof(QuickLinksSelectedIndex)); + } + catch (System.UnauthorizedAccessException) + { + } + } + } + + public void GoUp() + { + var parent = Path.GetDirectoryName(Location); + + if (string.IsNullOrWhiteSpace(parent)) + { + return; + } + + Navigate(parent); + } + + public void Cancel() + { + CancelRequested?.Invoke(); + } + + public void Ok() + { + if (_selectingDirectory) + { + CompleteRequested?.Invoke(new[] { Location }); + } + else if(_savingFile) + { + if (!string.IsNullOrWhiteSpace(FileName)) + { + if (!Path.HasExtension(FileName) && !string.IsNullOrWhiteSpace(_defaultExtension)) + { + FileName = Path.ChangeExtension(FileName, _defaultExtension); + } + + CompleteRequested?.Invoke(new[] { Path.Combine(Location, FileName) }); + } + } + else + { + CompleteRequested?.Invoke(SelectedItems.Select(i => i.Path).ToArray()); + } + } + + public void SelectSingleFile(ManagedFileChooserItemViewModel item) + { + CompleteRequested?.Invoke(new[] { item.Path }); + } + + public ReactiveCommand EnterLocationCommand { get; } + } } diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml index 34c8dba363..002f35ae24 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml +++ b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml @@ -2,6 +2,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal" xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal" - Title="{Binding Title}" MinWidth="700" MinHeight="500"> - + Icon="resm:WalletWasabi.Gui.Assets.WasabiLogo256.png?assembly=WalletWasabi.Gui" + Title="{Binding Title}" Width="1100" Height="500" MinWidth="1100" MinHeight="500" + FontFamily="{DynamicResource UiFont}" FontSize="14" + Foreground="{DynamicResource ThemeForegroundBrush}"> + diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs index 43276f5e90..34b0163808 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs @@ -1,18 +1,20 @@ using System; +using System.Runtime.InteropServices; +using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.Media; namespace Avalonia.Dialogs.Internal { - class ManagedFileDialog : Window - { - private ManagedFileChooserViewModel _model; - public ManagedFileDialog() - { - AvaloniaXamlLoader.Load(this); - #if DEBUG - this.AttachDevTools(); - #endif - } - } + class ManagedFileDialog : Window + { + public ManagedFileDialog() + { + AvaloniaXamlLoader.Load(this); +#if DEBUG + this.AttachDevTools(); +#endif + } + } } diff --git a/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs b/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs index a492dfed3a..8346ba952c 100644 --- a/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs +++ b/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs @@ -5,17 +5,17 @@ using Avalonia.Data.Converters; namespace Avalonia.Dialogs.Internal { - public class ResourceSelectorConverter : ResourceDictionary, IValueConverter - { - public object Convert(object key, Type targetType, object parameter, CultureInfo culture) - { - TryGetResource((string)key, out var value); - return value; - } + public class ResourceSelectorConverter : ResourceDictionary, IValueConverter + { + public object Convert(object key, Type targetType, object parameter, CultureInfo culture) + { + TryGetResource((string)key, out var value); + return value; + } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } } diff --git a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs index 26a11fc4ff..8c0aba5942 100644 --- a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs +++ b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Threading.Tasks; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Dialogs.Internal; @@ -7,48 +8,48 @@ using Avalonia.Platform; namespace Avalonia.Dialogs { - public static class ManagedFileDialogExtensions - { - class ManagedSystemDialogImpl : ISystemDialogImpl - { - async Task Show(SystemDialog d, IWindowImpl parent) - { - var model = new ManagedFileChooserViewModel((FileSystemDialog)d); - - var dialog = new ManagedFileDialog - { - DataContext = model - }; + public static class ManagedFileDialogExtensions + { + class ManagedSystemDialogImpl : ISystemDialogImpl + { + async Task Show(SystemDialog d, IWindowImpl parent) + { + var model = new ManagedFileChooserViewModel((FileSystemDialog)d); - string[] result = null; - model.CompleteRequested += items => - { - result = items; - dialog.Close(); - }; - model.CancelRequested += dialog.Close; + var dialog = new ManagedFileDialog + { + DataContext = model + }; - await dialog.ShowDialog(parent); - return result; - } - - public async Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) - { - return await Show(dialog, parent); - } + string[] result = null; + model.CompleteRequested += items => + { + result = items; + dialog.Close(); + }; + model.CancelRequested += dialog.Close; - public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) - { - return (await Show(dialog, parent))?.FirstOrDefault(); - } - } + await dialog.ShowDialog(parent); + return result; + } - public static TAppBuilder UseManagedSystemDialogs(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() - { - builder.AfterPlatformServicesSetup(_ => - AvaloniaLocator.CurrentMutable.Bind().ToSingleton()); - return builder; - } - } + public async Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) + { + return await Show(dialog, parent); + } + + public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + return (await Show(dialog, parent))?.FirstOrDefault(); + } + } + + public static TAppBuilder UseManagedSystemDialogs(this TAppBuilder builder) + where TAppBuilder : AppBuilderBase, new() + { + builder.AfterSetup(_ => + AvaloniaLocator.CurrentMutable.Bind().ToSingleton()); + return builder; + } + } } From 5cd0a43cbcae67130193ae5e70e1e5fdf055c6ee Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 Jul 2019 21:59:27 +0100 Subject: [PATCH 03/31] fix managed file dialog demo --- samples/ControlCatalog.NetCore/Program.cs | 18 +++++++++--------- samples/ControlCatalog/MainWindow.xaml.cs | 17 +++++++++++++++++ src/Avalonia.Dialogs/Avalonia.Dialogs.csproj | 7 ++----- .../Internal/ManagedFileChooser.xaml | 3 ++- .../Internal/ManagedFileDialog.xaml | 2 +- 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 0475e2b660..15ecfa5811 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -1,14 +1,15 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; -using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.LinuxFramebuffer.Output; using Avalonia.Skia; using Avalonia.ReactiveUI; +using Avalonia.Dialogs; +using System.Collections.Generic; +using System.Threading.Tasks; namespace ControlCatalog.NetCore { @@ -18,13 +19,10 @@ namespace ControlCatalog.NetCore static int Main(string[] args) { Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA); - var b = BuildAvaloniaApp(); - b.SetupWithoutStarting(); - var window = new Window(); - window.Show(); - new OpenFileDialog() + if (args.Contains("--wait-for-attach")) { - Filters = new List + Console.WriteLine("Attach debugger and use 'Set next statement'"); + while (true) { Thread.Sleep(100); if (Debugger.IsAttached) @@ -33,6 +31,7 @@ namespace ControlCatalog.NetCore } var builder = BuildAvaloniaApp(); + if (args.Contains("--fbdev")) { SilenceConsole(); @@ -60,7 +59,8 @@ namespace ControlCatalog.NetCore AllowEglInitialization = true }) .UseSkia() - .UseReactiveUI(); + .UseReactiveUI() + .UseManagedSystemDialogs(); static void SilenceConsole() { diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 91d9f034a5..ade5e4c2fc 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -6,6 +6,7 @@ using Avalonia.Markup.Xaml; using Avalonia.Threading; using ControlCatalog.ViewModels; using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace ControlCatalog @@ -28,6 +29,22 @@ namespace ControlCatalog }; DataContext = new MainWindowViewModel(_notificationArea); + + Dispatcher.UIThread.Post(() => + { + new OpenFileDialog() + { + Filters = new List + { + new FileDialogFilter {Name = "All files", Extensions = {"*"}}, + new FileDialogFilter {Name = "Image files", Extensions = {"jpg", "png", "gif"}} + }, + Directory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop), + Title = "My dialog", + InitialFileName = "config.local.json", + AllowMultiple = true + }.ShowAsync(this); + }); } private void InitializeComponent() diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj index b94b1db8bc..5b3e27eebd 100644 --- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj +++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj @@ -5,12 +5,9 @@ - + Designer - - - Designer - + diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml index cb7f2bef34..d048d45dbd 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml @@ -3,7 +3,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal" - xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal"> + xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal" + x:Class="Avalonia.Dialogs.Internal.ManagedFileChooser"> diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml index 002f35ae24..15fd8fcddb 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml +++ b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal" xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal" - Icon="resm:WalletWasabi.Gui.Assets.WasabiLogo256.png?assembly=WalletWasabi.Gui" + x:Class="Avalonia.Dialogs.Internal.ManagedFileDialog" Title="{Binding Title}" Width="1100" Height="500" MinWidth="1100" MinHeight="500" FontFamily="{DynamicResource UiFont}" FontSize="14" Foreground="{DynamicResource ThemeForegroundBrush}"> From fc1e033cc424276b17c1036935291f34ab247864 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 Jul 2019 22:14:09 +0100 Subject: [PATCH 04/31] remove need for reactiveCommand. --- src/Avalonia.Dialogs/Avalonia.Dialogs.csproj | 1 + .../Internal/ManagedFileChooser.xaml | 2 +- .../Internal/ManagedFileChooserViewModel.cs | 26 +++++++++---------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj index 5b3e27eebd..afe0d48f6d 100644 --- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj +++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml index d048d45dbd..24222600ef 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml @@ -39,7 +39,7 @@ - + diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs index 8f557a212d..aa81507fc1 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs @@ -145,20 +145,20 @@ namespace Avalonia.Dialogs.Internal Navigate(directory, (dialog as FileDialog)?.InitialFileName); SelectedItems.CollectionChanged += OnSelectionChangedAsync; - - EnterLocationCommand = ReactiveCommand.Create(() => - { - if (Directory.Exists(Location)) - { - Navigate(Location); - } - else if (File.Exists(Location)) - { - CompleteRequested?.Invoke(new[] { Location }); - } - }); } + public void EnterPressed () + { + if (Directory.Exists(Location)) + { + Navigate(Location); + } + else if (File.Exists(Location)) + { + CompleteRequested?.Invoke(new[] { Location }); + } + } + private async void OnSelectionChangedAsync(object sender, NotifyCollectionChangedEventArgs e) { if (_scheduledSelectionValidation) @@ -329,7 +329,5 @@ namespace Avalonia.Dialogs.Internal { CompleteRequested?.Invoke(new[] { item.Path }); } - - public ReactiveCommand EnterLocationCommand { get; } } } From d149a62dd33455dd8fc2bbb618a1b6a37afa9e26 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 25 Jul 2019 22:29:35 +0100 Subject: [PATCH 05/31] add grid splitter for resize of columns --- .../Internal/ManagedFileChooser.xaml | 220 +++++++++--------- 1 file changed, 112 insertions(+), 108 deletions(-) diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml index 24222600ef..5e02836173 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml @@ -5,127 +5,131 @@ xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal" xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal" x:Class="Avalonia.Dialogs.Internal.ManagedFileChooser"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Show hidden files - - - - - - - - - - - + + + + + + + + + + + + Show hidden files + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - From c685a9e3479e450f77252439dad85b9a3f9e00eb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 27 Jul 2019 10:38:39 +0100 Subject: [PATCH 06/31] fix for file dialog crashing with strange file name encoded strings. --- src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs index aa81507fc1..3977874e93 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs @@ -254,7 +254,9 @@ namespace Avalonia.Dialogs.Internal } return true; - }).Select(info => new ManagedFileChooserItemViewModel + }) + .Where(x => x.Exists) + .Select(info => new ManagedFileChooserItemViewModel { DisplayName = info.Name, Path = info.FullName, From f5c85674591f4e89a6a29da9e433b680b517006f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 27 Jul 2019 10:58:27 +0100 Subject: [PATCH 07/31] allow use of custom window for hosting file dialogs --- .../Internal/ManagedFileChooser.xaml | 2 +- .../Internal/ManagedFileDialog.xaml | 10 --- .../Internal/ManagedFileDialog.xaml.cs | 20 ----- .../ManagedFileDialogExtensions.cs | 82 +++++++++++-------- 4 files changed, 47 insertions(+), 67 deletions(-) delete mode 100644 src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml delete mode 100644 src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml index 5e02836173..2e694e726d 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml @@ -4,7 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal" xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal" - x:Class="Avalonia.Dialogs.Internal.ManagedFileChooser"> + x:Class="Avalonia.Dialogs.Internal.ManagedFileChooser" Margin="12 0 12 6"> diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml deleted file mode 100644 index 15fd8fcddb..0000000000 --- a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs b/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs deleted file mode 100644 index 34b0163808..0000000000 --- a/src/Avalonia.Dialogs/Internal/ManagedFileDialog.xaml.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using Avalonia.Media; - -namespace Avalonia.Dialogs.Internal -{ - class ManagedFileDialog : Window - { - public ManagedFileDialog() - { - AvaloniaXamlLoader.Load(this); -#if DEBUG - this.AttachDevTools(); -#endif - } - } -} diff --git a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs index 8c0aba5942..864de4b3f6 100644 --- a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs +++ b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Threading.Tasks; using Avalonia; @@ -10,46 +11,55 @@ namespace Avalonia.Dialogs { public static class ManagedFileDialogExtensions { - class ManagedSystemDialogImpl : ISystemDialogImpl - { - async Task Show(SystemDialog d, IWindowImpl parent) - { - var model = new ManagedFileChooserViewModel((FileSystemDialog)d); - - var dialog = new ManagedFileDialog - { - DataContext = model - }; - - string[] result = null; - model.CompleteRequested += items => - { - result = items; - dialog.Close(); - }; - model.CancelRequested += dialog.Close; - - await dialog.ShowDialog(parent); - return result; - } - - public async Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) - { - return await Show(dialog, parent); - } - - public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) - { - return (await Show(dialog, parent))?.FirstOrDefault(); - } - } + class CustomWindowManagedSystemDialogImpl : ISystemDialogImpl where T : Window, new() + { + async Task Show(SystemDialog d, IWindowImpl parent) + { + var model = new ManagedFileChooserViewModel((FileSystemDialog)d); + + var dialog = new T + { + Content = new ManagedFileChooser(), + DataContext = model + }; + + string[] result = null; + model.CompleteRequested += items => + { + result = items; + dialog.Close(); + }; + model.CancelRequested += dialog.Close; - public static TAppBuilder UseManagedSystemDialogs(this TAppBuilder builder) + await dialog.ShowDialog(parent); + return result; + } + + public async Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) + { + return await Show(dialog, parent); + } + + public async Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + return (await Show(dialog, parent))?.FirstOrDefault(); + } + } + + public static TAppBuilder UseManagedSystemDialogs(this TAppBuilder builder) where TAppBuilder : AppBuilderBase, new() { builder.AfterSetup(_ => - AvaloniaLocator.CurrentMutable.Bind().ToSingleton()); + AvaloniaLocator.CurrentMutable.Bind().ToSingleton>()); return builder; } - } + + public static TAppBuilder UseManagedSystemDialogs(this TAppBuilder builder) + where TAppBuilder : AppBuilderBase, new() where TWindow : Window, new() + { + builder.AfterSetup(_ => + AvaloniaLocator.CurrentMutable.Bind().ToSingleton>()); + return builder; + } + } } From 24ae6ce1ad8bac1de2e2dbf16727262ba7fe313c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 27 Jul 2019 10:59:54 +0100 Subject: [PATCH 08/31] [Managed Dialogs] make classes internal --- src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs | 6 +----- src/Avalonia.Dialogs/Internal/ChildFitter.cs | 2 +- src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs | 2 +- src/Avalonia.Dialogs/Internal/InternalViewModelBase.cs | 2 +- src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs | 2 +- .../Internal/ManagedFileChooserFilterViewModel.cs | 2 +- .../Internal/ManagedFileChooserItemViewModel.cs | 2 +- .../Internal/ManagedFileChooserNavigationItem.cs | 2 +- src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs | 2 +- .../Internal/ManagedFileChooserViewModel.cs | 4 +--- src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs | 2 +- 11 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs b/src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs index 3517f760b4..d8035daf40 100644 --- a/src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs +++ b/src/Avalonia.Dialogs/Internal/ByteSizeHelper.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Text; - namespace Avalonia.Dialogs.Internal { - public static class ByteSizeHelper + internal static class ByteSizeHelper { private static readonly string[] Prefixes = { diff --git a/src/Avalonia.Dialogs/Internal/ChildFitter.cs b/src/Avalonia.Dialogs/Internal/ChildFitter.cs index 6f9baff82f..5d0f89f3ac 100644 --- a/src/Avalonia.Dialogs/Internal/ChildFitter.cs +++ b/src/Avalonia.Dialogs/Internal/ChildFitter.cs @@ -4,7 +4,7 @@ using Avalonia.Layout; namespace Avalonia.Dialogs.Internal { - class ChildFitter : Decorator + internal class ChildFitter : Decorator { protected override Size MeasureOverride(Size availableSize) { diff --git a/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs b/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs index 6de931c89d..e526932829 100644 --- a/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs +++ b/src/Avalonia.Dialogs/Internal/FileSizeStringConverter.cs @@ -6,7 +6,7 @@ using System.Text; namespace Avalonia.Dialogs.Internal { - public class FileSizeStringConverter : IValueConverter + internal class FileSizeStringConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { diff --git a/src/Avalonia.Dialogs/Internal/InternalViewModelBase.cs b/src/Avalonia.Dialogs/Internal/InternalViewModelBase.cs index 35e37eb810..2a8ae67ce1 100644 --- a/src/Avalonia.Dialogs/Internal/InternalViewModelBase.cs +++ b/src/Avalonia.Dialogs/Internal/InternalViewModelBase.cs @@ -5,7 +5,7 @@ using JetBrains.Annotations; namespace Avalonia.Dialogs.Internal { - class InternalViewModelBase : INotifyPropertyChanged + internal class InternalViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs index 7aba87115d..d476d226b0 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml.cs @@ -10,7 +10,7 @@ using Avalonia.Markup.Xaml; namespace Avalonia.Dialogs.Internal { - class ManagedFileChooser : UserControl + internal class ManagedFileChooser : UserControl { private Control _quickLinksRoot; private ListBox _filesView; diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs index d54945ac60..b86b7a3f72 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs @@ -5,7 +5,7 @@ using Avalonia.Controls; namespace Avalonia.Dialogs.Internal { - class ManagedFileChooserFilterViewModel : InternalViewModelBase + internal class ManagedFileChooserFilterViewModel : InternalViewModelBase { private readonly string[] _extensions; public string Name { get; } diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs index b6535a93d0..306643b018 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserItemViewModel.cs @@ -2,7 +2,7 @@ using System; namespace Avalonia.Dialogs.Internal { - class ManagedFileChooserItemViewModel : InternalViewModelBase + internal class ManagedFileChooserItemViewModel : InternalViewModelBase { private string _displayName; private string _path; diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs index b04a36692c..d3d5e2292b 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserNavigationItem.cs @@ -1,6 +1,6 @@ namespace Avalonia.Dialogs.Internal { - public class ManagedFileChooserNavigationItem + internal class ManagedFileChooserNavigationItem { public string DisplayName { get; set; } public string Path { get; set; } diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs index 84a3173eac..ad99206789 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserSources.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; namespace Avalonia.Dialogs.Internal { - public class ManagedFileChooserSources + internal class ManagedFileChooserSources { public Func GetUserDirectories { get; set; } = DefaultGetUserDirectories; diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs index 3977874e93..b6e61bd18e 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs @@ -2,16 +2,14 @@ using System; using System.Collections.Specialized; using System.IO; using System.Linq; -using System.Reactive; using System.Runtime.InteropServices; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Threading; -using ReactiveUI; namespace Avalonia.Dialogs.Internal { - class ManagedFileChooserViewModel : InternalViewModelBase + internal class ManagedFileChooserViewModel : InternalViewModelBase { public event Action CancelRequested; public event Action CompleteRequested; diff --git a/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs b/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs index 8346ba952c..11ef6c0f5b 100644 --- a/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs +++ b/src/Avalonia.Dialogs/Internal/ResourceSelectorConverter.cs @@ -5,7 +5,7 @@ using Avalonia.Data.Converters; namespace Avalonia.Dialogs.Internal { - public class ResourceSelectorConverter : ResourceDictionary, IValueConverter + internal class ResourceSelectorConverter : ResourceDictionary, IValueConverter { public object Convert(object key, Type targetType, object parameter, CultureInfo culture) { From 91d570bbf6ccfe9f7514725294f73d142e806bf4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 27 Jul 2019 11:00:15 +0100 Subject: [PATCH 09/31] [Managed Dialogs] Dont depend on rx and rxui --- src/Avalonia.Dialogs/Avalonia.Dialogs.csproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj index afe0d48f6d..8b48b4a92c 100644 --- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj +++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj @@ -10,8 +10,6 @@ - - From 09dd7c67c195fabaf9d4ab33c9c4d51759e431bd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 27 Jul 2019 11:09:33 +0100 Subject: [PATCH 10/31] some polishing of file dialog --- .../Internal/ManagedFileChooser.xaml | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml index 2e694e726d..7fedf2ec30 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooser.xaml @@ -4,7 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:dialogs="clr-namespace:Avalonia.Dialogs.Internal" xmlns:internal="clr-namespace:Avalonia.Dialogs.Internal" - x:Class="Avalonia.Dialogs.Internal.ManagedFileChooser" Margin="12 0 12 6"> + x:Class="Avalonia.Dialogs.Internal.ManagedFileChooser" Margin="10"> @@ -30,7 +30,7 @@ - +